Mark24
React关键问题01
基础问题
1. 什么是React
React 是一个开源前端 JavaScript 库,用于构建用户界面,尤其是单页应用程序。它用于处理网页和移动应用程序的视图层。
2. React的主要特点是什么
React 的主要特性有:
考虑到真实的 DOM 操作成本很高,它使用 VirtualDOM 而不是真实的 DOM。
支持服务端渲染。
遵循单向数据流或数据绑定。
使用可复用/可组合的 UI 组件开发视图。
3. 什么是JSX?
JSX 是 ECMAScript 一个类似 XML 的语法扩展。基本上,它只是为 React.createElement() 函数提供语法糖,从而让在我们在 JavaScript 中,使用类 HTML 模板的语法,进行页面描述。
在下面的示例中,<h1> 内的文本标签会作为 JavaScript 函数返回给渲染函数。
class App extends React.Component {
render() {
return(
<div>
<h1>{'Welcome to React world!'}</h1>
</div>
)
}
}
以上示例 render 方法中的 JSX 将会被转换为以下内容:
React.createElement("div", null, React.createElement(
"h1", null, 'Welcome to React world!'));
这里你可以访问 Babeljs 在线体验一下。
参考:
4. 元素和组件有什么区别?
一个 Element 是一个简单的对象,它描述了你希望在屏幕上以 DOM 节点或其他组件的形式呈现的内容。Elements 在它们的属性中可以包含其他 Elements。创建一个 React 元素是很轻量的。一旦元素被创建后,它将不会被修改。
React Element 的对象表示如下:
const element = React.createElement(
'div',
{id: 'login-btn'},
'Login'
)
上面的 React.createElement()
函数会返回一个对象。
{
type: 'div',
props: {
children: 'Login',
id: 'login-btn'
}
}
最后使用 ReactDOM.render() 方法渲染到 DOM:
<div id='login-btn'>Login</div>
而一个组件可以用多种不同方式声明。它可以是一个含有 render() 方法的类。或者,在简单的情况中,它可以定义为函数。无论哪种情况,它都将 props 作为输入,并返回一个 JSX 树作为输出:
const Button = ({ onLogin }) =>
<div id={'login-btn'} onClick={onLogin} />
然后 JSX 被转换成 React.createElement() 函数:
const Button = ({ onLogin }) => React.createElement(
'div',
{ id: 'login-btn', onClick: onLogin },
'Login'
)
参考
5. 如何在 React 中创建组件?
- Function Components: 这是创建组件最简单的方式。这些是纯 JavaScript 函数,接受 props 对象作为第一个参数并返回 React 元素
function Greeting({ message }) {
return <h1>{`Hello, ${message}`}</h1>
}
- Class Components: 你还可以使用 ES6 类来定义组件。上面的函数组件若使用 ES6 的类可改写为:
class Greeting extends React.Component {
render() {
return <h1>{`Hello, ${this.props.message}`}</h1>
}
}
通过以上任意方式创建的组件,可以这样使用:
<Greeting message="semlinker"/>
在 React 内部对函数组件和类组件的处理方式是不一样的,如:
// 如果 Greeting 是一个函数
const result = Greeting(props); // <p>Hello</p>
// 如果 Greeting 是一个类
const instance = new Greeting(props); // Greeting {}
const result = instance.render(); // <p>Hello</p>
拓展
什么是 Pure Components?
React.PureComponent 与 React.Component 完全相同,只是它为你处理了 shouldComponentUpdate() 方法。当属性或状态发生变化时,PureComponent 将对属性和状态进行浅比较。另一方面,一般的组件不会将当前的属性和状态与新的属性和状态进行比较。因此,在默认情况下,每当调用 shouldComponentUpdate 时,默认返回 true,所以组件都将重新渲染。
状态和属性有什么区别?
state 和 props 都是普通的 JavaScript 对象。虽然它们都保存着影响渲染输出的信息,但它们在组件方面的功能不同。Props 以类似于函数参数的方式传递给组件,而状态则类似于在函数内声明变量并对它进行管理。
回调函数作为 setState() 参数的目的是什么?
当 setState 完成和组件渲染后,回调函数将会被调用。由于 setState() 是异步的,回调函数用于任何后续的操作。
HTML 和 React 事件处理有什么区别?
1.在 HTML 中事件名必须小写:
<button onclick='activateLasers()'>
而在 React 中它遵循 camelCase (驼峰) 惯例:
<button onClick={activateLasers}>
2.在 HTML 中你可以返回 false 以阻止默认的行为:
<a href='#' onclick='console.log("The link was clicked."); return false;' />
而在 React 中你必须地明确地调用 preventDefault() :
function handleClick(event) {
event.preventDefault()
console.log('The link was clicked.')
}
如何在 JSX 回调中绑定方法或事件处理程序?
实现这一点有三种可能的方法:
- Binding in Constructor: 在 JavaScript 类中,方法默认不被绑定。这也适用于定义为类方法的 React 事件处理程序。通常我们在构造函数中绑定它们。
class Component extends React.Componenet {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
// ...
}
}
- Public class fields syntax: 如果你不喜欢 bind 方案,则可以使用 public class fields syntax 正确绑定回调。
handleClick = () => {
console.log('this is:', this)
}
<button onClick={this.handleClick}>
{'Click me'}
</button>
- Arrow functions in callbacks: 你可以在回调函数中直接使用 arrow functions。
<button onClick={(event) => this.handleClick(event)}>
{'Click me'}
</button>
注意: 如果回调函数作为属性传给子组件,那么这些组件可能触发一个额外的重新渲染。在这些情况下,考虑到性能,最好使用 .bind() 或 public class fields syntax 方案。
如何将参数传递给事件处理程序或回调函数?
你可以使用箭头函数来包装事件处理器并传递参数:
<button onClick={() => this.handleClick(id)} />
这相当于调用 .bind
:
<button onClick={this.handleClick.bind(this, id)} />
React 中的合成事件是什么?
SyntheticEvent
是对浏览器原生事件的跨浏览器包装。它的 API 与浏览器的原生事件相同,包括 stopPropagation()
和 preventDefault()
,除了事件在所有浏览器中的工作方式相同。
什么是内联条件表达式?
在 JS 中你可以使用 if 语句或三元表达式,来实现条件判断。除了这些方法之外,你还可以在 JSX 中嵌入任何表达式,方法是将它们用大括号括起来,然后再加上 JS 逻辑运算符 &&。
<h1>Hello!</h1>
{
messages.length > 0 && !isLogin ?
<h2>
You have {messages.length} unread messages.
</h2>
:
<h2>
You don't have unread messages.
</h2>
}
当然如果只是想判断 if,可以如下直接判断:
{
isLogin && <span>Your have been login!</span>
}
什么是 “key” 属性,在元素数组中使用它们有什么好处?
key 是一个特殊的字符串属性,你在创建元素数组时需要包含它。Keys 帮助 React 识别哪些项已更改、添加或删除。
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
)
在渲染列表项时,如果你没有稳定的 IDs,你可能会使用 index 作为 key:
const todoItems = todos.map((todo, index) =>
<li key={index}>
{todo.text}
</li>
)
注意
由于列表项的顺序可能发生改变,因此并不推荐使用 indexes 作为 keys。这可能会对性能产生负面影响,并可能导致组件状态出现问题。 如果将列表项提取为单独的组件,则在列表组件上应用 keys 而不是 li 标签。 如果在列表项中没有设置 key 属性,在控制台会显示警告消息。
refs 有什么用?
ref 用于返回对元素的引用。但在大多数情况下,应该避免使用它们。当你需要直接访问 DOM 元素或组件的实例时,它们可能非常有用。
什么是 forward refs?
Ref forwarding 是一个特性,它允许一些组件获取接收到 ref 对象并将它进一步传递给子组件。
const ButtonElement = React.forwardRef((props, ref) => (
<button ref={ref} className="CustomButton">
{props.children}
</button>
));
// Create ref to the DOM button:
const ref = React.createRef();
<ButtonElement ref={ref}>{'Forward Ref'}</ButtonElement>
callback refs 和 findDOMNode() 哪一个是首选选项?
最好是使用 callback refs 而不是 findDOMNode() API。因为 findDOMNode() 阻碍了将来对 React 的某些改进。
使用 findDOMNode 已弃用的方案:
class MyComponent extends Component {
componentDidMount() {
findDOMNode(this).scrollIntoView()
}
render() {
return <div />
}
}
推荐的方案是:
class MyComponent extends Component {
componentDidMount() {
this.node.scrollIntoView()
}
render() {
return <div ref={node => this.node = node} />
}
}
为什么 String Refs 被弃用?
如果你以前使用过 React,你可能会熟悉旧的 API,其中的 ref 属性是字符串,如 ref={‘textInput’},并且 DOM 节点的访问方式为this.refs.textInput。我们建议不要这样做,因为字符串引用有以下问题,并且被认为是遗留问题。字符串 refs 在 React v16 版本中被移除。
* 它们强制 React 跟踪当前执行的组件。这是有问题的,因为它使 React 模块有状态,这会导致在 bundle 中复制 React 模块时会导致奇怪的错误。
* 它们是不可组合的 - 如果一个库把一个 ref 传给子元素,则用户无法对其设置另一个引用。
* 它们不能与静态分析工具一起使用,如 Flow。Flow 无法猜测出 this.refs 上的字符串引用的作用及其类型。Callback refs 对静态分析更友好。
* 使用 "render callback" 模式(比如: ),它无法像大多数人预期的那样工作。
class MyComponent extends Component {
renderRow = (index) => {
// This won't work. Ref will get attached to DataTable rather than MyComponent:
return <input ref={'input-' + index} />;
// This would work though! Callback refs are awesome.
return <input ref={input => this['input-' + index] = input} />;
}
render() {
return <DataTable data={this.props.data} renderRow={this.renderRow} />
}
}
什么是 Virtual DOM?
Virtual DOM (VDOM) 是 Real DOM 的内存表示形式。UI 的展示形式被保存在内存中并与真实的 DOM 同步。这是在调用的渲染函数和在屏幕上显示元素之间发生的一个步骤。整个过程被称为 reconciliation。
Virtual DOM 如何工作?
Virtual DOM 分为三个简单的步骤。
- 每当任何底层数据发生更改时,整个 UI 都将以 Virtual DOM 的形式重新渲染。
- 然后计算新的 Virtual DOM 和 旧 Virtual DOM 对象之间的差异。
- 一旦计算完成,真实的 DOM 将只更新实际更改的内容
什么是 React Fiber?
Fiber 是 React v16 中新的 reconciliation 引擎,或核心算法的重新实现。React Fiber 的目标是提高对动画,布局,手势,暂停,中止或者重用任务的能力及为不同类型的更新分配优先级,及新的并发原语等领域的适用性。
React Fiber 的主要目标是什么?
React Fiber 的目标是提高其在动画、布局和手势等领域的适用性。它的主要特性是 incremental rendering: 将渲染任务拆分为小的任务块并将任务分配到多个帧上的能力。
什么是受控组件?
在随后的用户输入中,能够控制表单中输入元素的组件被称为受控组件,即每个状态更改都有一个相关联的处理程序。
例如,我们使用下面的 handleChange 函数将输入框的值转换成大写:
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()})
}
什么是非受控组件?
非受控组件是在内部存储其自身状态的组件,当需要时,可以使用 ref 查询 DOM 并查找其当前值。这有点像传统的 HTML。
在下面的 UserProfile 组件中,我们通过 ref 引用 name 输入框:
class UserProfile extends React.Component {
constructor(props) {
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
this.input = React.createRef()
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value)
event.preventDefault()
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
{'Name:'}
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
在大多数情况下,建议使用受控组件来实现表单。
createElement 和 cloneElement 有什么区别?
JSX 元素将被转换为 React.createElement() 函数来创建 React 元素,这些对象将用于表示 UI 对象。而 cloneElement 用于克隆元素并传递新的属性。
在 React 中的提升状态是什么?
当多个组件需要共享相同的更改数据时,建议将共享状态提升到最接近的共同祖先。这意味着,如果两个子组件共享来自其父组件的相同数据,则将状态移动到父组件,而不是在两个子组件中维护局部状态。
组件生命周期的不同阶段是什么?
* Mounting
组件已准备好挂载到浏览器的 DOM 中. 此阶段包含来自 constructor(), getDerivedStateFromProps(), render(), 和 componentDidMount() 生命周期方法中的初始化过程。
* Updating
在此阶段,组件以两种方式更新,发送新的属性并使用 setState() 或 forceUpdate() 方法更新状态. 此阶段包含 getDerivedStateFromProps(), shouldComponentUpdate(), render(), getSnapshotBeforeUpdate() 和 componentDidUpdate() 生命周期方法。
## * Unmounting
在这个最后阶段,不需要组件,它将从浏览器 DOM 中卸载。这个阶段包含 componentWillUnmount() 生命周期方法。
值得一提的是,在将更改应用到 DOM 时,React 内部也有阶段概念。它们按如下方式分隔开:
-
Render 组件将会进行无副作用渲染。这适用于纯组件(Pure Component),在此阶段,React 可以暂停,中止或重新渲染。
-
Pre-commit 在组件实际将更改应用于 DOM 之前,有一个时刻允许 React 通过getSnapshotBeforeUpdate()捕获一些 DOM 信息(例如滚动位置)。
-
Commit React 操作 DOM 并分别执行最后的生命周期: componentDidMount() 在 DOM 渲染完成后调用, componentDidUpdate() 在组件更新时调用, componentWillUnmount() 在组件卸载时调用。 React 16.3+ 阶段 (也可以看{交互式版本](projects.wojtekmaj.pl/react-lifecycle-methods-diagram/))
React 16.3 之后
React 16.3 之前
React 生命周期方法有哪些?
React 16.3+
- getDerivedStateFromProps: 在调用render()之前调用,并在 每次 渲染时调用。 需要使用派生状态的情况是很罕见得。值得阅读 如果你需要派生状态.
- componentDidMount: 首次渲染后调用,所有得 Ajax 请求、DOM 或状态更新、设置事件监听器都应该在此处发生。
- shouldComponentUpdate: 确定组件是否应该更新。 默认情况下,它返回true。 如果你确定在更新状态或属性后不需要渲染组件,则可以返回false值。 它是一个提高性能的好地方,因为它允许你在组件接收新属性时阻止重新渲染。
- getSnapshotBeforeUpdate: 在最新的渲染输出提交给 DOM 前将会立即调用,这对于从 DOM 捕获信息(比如:滚动位置)很有用。
- componentDidUpdate: 它主要用于更新 DOM 以响应 prop 或 state 更改。 如果shouldComponentUpdate()返回false,则不会触发。
- componentWillUnmount 当一个组件被从 DOM 中移除时,该方法被调用,取消网络请求或者移除与该组件相关的事件监听程序等应该在这里进行。
React 16.3 之前
- componentWillMount: 在组件render()前执行,用于根组件中的应用程序级别配置。应该避免在该方法中引入任何的副作用或订阅。
- componentDidMount: 首次渲染后调用,所有得 Ajax 请求、DOM 或状态更新、设置事件监听器都应该在此处发生。
- componentWillReceiveProps: 在组件接收到新属性前调用,若你需要更新状态响应属性改变(例如,重置它),你可能需对比this.props和nextProps并在该方法中使用this.setState()处理状态改变。
- shouldComponentUpdate: 确定组件是否应该更新。 默认情况下,它返回true。 如果你确定在更新状态或属性后不需要渲染组件,则可以返回false值。 它是一个提高性能的好地方,因为它允许你在组件接收新属性时阻止重新渲染。
- componentWillUpdate: 当shouldComponentUpdate返回true后重新渲染组件之前执行,注意你不能在这调用this.setState()
- componentDidUpdate: 它主要用于更新 DOM 以响应 prop 或 state 更改。 如果shouldComponentUpdate()返回false,则不会触发。
- componentWillUnmount: 当一个组件被从 DOM 中移除时,该方法被调用,取消网络请求或者移除与该组件相关的事件监听程序等应该在这里进行。
什么是高阶组件(HOC)?
高阶组件(HOC) 就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件,它只是一种模式,这种模式是由react自身的组合性质必然产生的。
我们将它们称为纯组件,因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件中的任何行为。
const EnhancedComponent = higherOrderComponent(WrappedComponent)
HOC 有很多用例:
- 代码复用,逻辑抽象化
- 渲染劫持
- 抽象化和操作状态(state)
- 操作属性(props)
拓展
如何为高阶组件创建属性代理?
你可以使用属性代理模式向输入组件增加或编辑属性(props):
function HOC(WrappedComponent) {
return class Test extends Component {
render() {
const newProps = {
title: 'New Header',
footer: false,
showFeatureX: false,
showFeatureY: true
};
return <WrappedComponent {...this.props} {...newProps} />
}
}
}
什么是上下文(Context)?
Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递props。比如,需要在应用中许多组件需要访问登录用户信息、地区偏好、UI主题等。
// 创建一个 theme Context, 默认 theme 的值为 light
const ThemeContext = React.createContext('light');
function ThemedButton(props) {
// ThemedButton 组件从 context 接收 theme
return (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
}
// 中间组件
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
构造函数使用带 props 参数的目的是什么?
在调用super()方法之前,子类构造函数不能使用this引用。这同样适用于ES6子类。将props参数传递给super()的主要原因是为了在子构造函数中访问this.props。
带 props 参数:
class MyComponent extends React.Component {
constructor(props) {
super(props)
console.log(this.props) // prints { name: 'John', age: 42 }
}
}
不带 props 参数:
class MyComponent extends React.Component {
constructor(props) {
super()
console.log(this.props) // prints undefined
// but props parameter is still available
console.log(props) // prints { name: 'John', age: 42 }
}
render() {
// no difference outside constructor
console.log(this.props) // prints { name: 'John', age: 42 }
}
}
上面的代码片段显示this.props仅在构造函数中有所不同。 它在构造函数之外是相同的。
什么是调解/协调(Reconciliation)
当组件的props或state发生更改时,React 通过将新返回的元素与先前呈现的元素进行比较来确定是否需要实际的 DOM 更新。当它们不相等时,React 将更新 DOM 。此过程称为reconciliation。
如何使用动态属性名设置 state ?
如果你使用 ES6 或 Babel 转换器来转换你的 JSX 代码,那么你可以使用计算属性名称来完成此操作。
handleInputChange(event) {
this.setState({ [event.target.id]: event.target.value })
}
每次组件渲染时调用函数的常见错误是什么?
你需要确保在将函数作为参数传递时未调用该函数。
render() {
// Wrong: handleClick is called instead of passed as a reference!
return <button onClick={this.handleClick()}>{'Click Me'}</button>
}
相反地,传递函数本身应该没有括号:
render() {
// Correct: handleClick is passed as a reference!
return <button onClick={this.handleClick}>{'Click Me'}</button>
}
为什么有组件名称要首字母大写?
这是必要的,因为组件不是 DOM 元素,它们是构造函数。 此外,在 JSX 中,小写标记名称是指 HTML 元素,而不是组件。
为什么 React 使用 className 而不是 class 属性?
class 是 JavaScript 中的关键字,而 JSX 是 JavaScript 的扩展。这就是为什么 React 使用 className 而不是 class 的主要原因。传递一个字符串作为 className 属性。
什么是 Fragments ?
它是 React 中的常见模式,用于组件返回多个元素。Fragments 可以让你聚合一个子元素列表,而无需向 DOM 添加额外节点。
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
)
}
以下是简洁语法,但是在一些工具中还不支持:
render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
)
}
译注:React 16 以前,render 函数的返回必须有一个根节点,否则报错。
为什么使用 Fragments 比使用容器 div 更好?
- 通过不创建额外的 DOM 节点,Fragments 更快并且使用更少的内存。这在非常大而深的节点树时很有好处。
- 一些 CSS 机制如Flexbox和CSS Grid具有特殊的父子关系,如果在中间添加 div 将使得很难保持所需的结构。
- 在 DOM 审查器中不会那么的杂乱。
在 React 中什么是 Portal ?
Portal 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。
ReactDOM.createPortal(child, container)
第一个参数是任何可渲染的 React 子节点,例如元素,字符串或片段。第二个参数是 DOM 元素。
什么是无状态组件?
如果行为独立于其状态,则它可以是无状态组件。你可以使用函数或类来创建无状态组件。但除非你需要在组件中使用生命周期钩子,否则你应该选择函数组件。无状态组件有很多好处: 它们易于编写,理解和测试,速度更快,而且你可以完全避免使用this关键字。
什么是有状态组件?
如果组件的行为依赖于组件的state,那么它可以被称为有状态组件。
React 的优点是什么?
- 使用 Virtual DOM 提高应用程序的性能。
- JSX 使代码易于读写。
- 它支持在客户端和服务端渲染。
- 易于与框架(Angular,Backbone)集成,因为它只是一个视图库。
- 使用 Jest 等工具轻松编写单元与集成测试。
- 组件化框架,可以构建复杂应用
React 的局限性是什么?
- React 只是一个视图库,而不是一个完整的框架。
- 对于 Web 开发初学者来说,有一个学习曲线。
- 将 React 集成到传统的 MVC 框架中需要一些额外的配置。
- 代码复杂性随着内联模板和 JSX 的增加而增加。
在 React v16 中的错误边界是什么
错误边界是在其子组件树中的任何位置捕获 JavaScript 错误、记录这些错误并显示回退 UI 而不是崩溃的组件树的组件。
如果一个类组件定义了一个名为 componentDidCatch(error, info) 或 static getDerivedStateFromError() 新的生命周期方法,则该类组件将成为错误边界:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}
componentDidCatch(error, info) {
// You can also log the error to an error reporting service
logErrorToMyService(error, info)
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>{'Something went wrong.'}</h1>
}
return this.props.children
}
}
之后,将其作为常规组件使用:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
在 React v15 中如何处理错误边界?
React v15 使用 unstable_handleError 方法为错误边界提供了非常基础的支持。已在 React v16 中,将其重命名为componentDidCatch。
静态类型检查推荐的方法是什么?
通常,我们使用 PropTypes 库(在 React v15.5 之后 React.PropTypes 被移动到了 prop-types 包中),在 React 应用程序中执行类型检查。对于大型项目,建议使用静态类型检查器,比如 Flow 或 TypeScript,它们在编译时执行类型检查并提供 auto-completion 功能。
react-dom 中 render 方法的目的是什么?
此方法用于将 React 元素渲染到所提供容器中的 DOM 结构中,并返回对组件的引用。如果 React 元素之前已被渲染到容器中,它将对其执行更新,并且只在需要时改变 DOM 以反映最新的更改。
ReactDOM.render(element, container[, callback])
ReactDOMServer 是什么?
ReactDOMServer 对象使你能够将组件渲染为静态标记(通常用于 Node 服务器中),此对象主要用于服务端渲染(SSR)。以下方法可用于服务器和浏览器环境:
renderToString()
renderToStaticMarkup()
例如,你通常运行基于 Node 的 Web 服务器,如 Express,Hapi 或 Koa,然后你调用 renderToString 将根组件渲染为字符串,然后作为响应进行发送。
// using Express
import { renderToString } from 'react-dom/server'
import MyPage from './MyPage'
app.get('/', (req, res) => {
res.write('<!DOCTYPE html><html><head><title>My Page</title></head><body>')
res.write('<div id="content">')
res.write(renderToString(<MyPage/>))
res.write('</div></body></html>')
res.end()
})
在 React 中如何使用 innerHTML?
dangerouslySetInnerHTML 属性是 React 用来替代在浏览器 DOM 中使用 innerHTML。与 innerHTML 一样,考虑到跨站脚本攻击(XSS),使用此属性也是有风险的。使用时,你只需传递以 __html 作为键,而 HTML 文本作为对应值的对象。
在本示例中 MyComponent 组件使用 dangerouslySetInnerHTML 属性来设置 HTML 标记:
function createMarkup() {
return { __html: 'First · Second' }
}
function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />
}
在 React 中事件有何不同?
处理 React 元素中的事件有一些语法差异:
- React 事件处理程序是采用驼峰而不是小写来命名的。
- 使用 JSX,你将传递一个函数作为事件处理程序,而不是字符串。
如果在构造函数中使用 setState() 会发生什么?
当你使用 setState() 时,除了设置状态对象之外,React 还会重新渲染组件及其所有的子组件。你会得到这样的错误:Can only update a mounted or mounting component.。因此我们需要在构造函数中使用 this.state 初始化状态。
在 componentWillMount() 方法中使用 setState() 好吗?
建议避免在 componentWillMount() 生命周期方法中执行异步初始化。在 mounting 发生之前会立即调用 componentWillMount(),且它在 render() 之前被调用,因此在此方法中更新状态将不会触发重新渲染。应避免在此方法中引入任何副作用或订阅操作。我们需要确保对组件初始化的异步调用发生在 componentDidMount() 中,而不是在 componentWillMount() 中
componentDidMount() {
axios.get(`api/todos`)
.then((result) => {
this.setState({
messages: [...result.data]
})
})
}
如果在初始状态中使用 props 属性会发生什么?
如果在不刷新组件的情况下更改组件上的属性,则不会显示新的属性值,因为构造函数函数永远不会更新组件的当前状态。只有在首次创建组件时才会用 props 属性初始化状态。
以下组件将不显示更新的输入值:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
records: [],
inputValue: this.props.inputValue
};
}
render() {
return <div>{this.state.inputValue}</div>
}
}
在 render 方法使用使用 props 将会显示更新的值:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
record: []
}
}
render() {
return <div>{this.props.inputValue}</div>
}
}
如何实现 Server Side Rendering 或 SSR?
React 已经配备了用于处理 Node 服务器上页面渲染的功能。你可以使用特殊版本的 DOM 渲染器,它遵循与客户端相同的模式。
import ReactDOMServer from 'react-dom/server'
import App from './App'
ReactDOMServer.renderToString(<App />)
此方法将以字符串形式输出常规 HTML,然后将其作为服务器响应的一部分放在页面正文中。在客户端,React 检测预渲染的内容并无缝地衔接。
在 mounting 阶段生命周期方法的执行顺序是什么?
在创建组件的实例并将其插入到 DOM 中时,将按以下顺序调用生命周期方法。
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
命周期方法 getDerivedStateFromProps() 的目的是什么?
新的静态 getDerivedStateFromProps() 生命周期方法在实例化组件之后以及重新渲染组件之前调用。它可以返回一个对象用于更新状态,或者返回 null 指示新的属性不需要任何状态更新。
class MyComponent extends React.Component {
static getDerivedStateFromProps(props, state) {
// ...
}
}
此生命周期方法与 componentDidUpdate() 一起涵盖了 componentWillReceiveProps() 的所有用例。
拓展
生命周期方法 getSnapshotBeforeUpdate() 的目的是什么?
新的 getSnapshotBeforeUpdate() 生命周期方法在 DOM 更新之前被调用。此方法的返回值将作为第三个参数传递给componentDidUpdate()。
class MyComponent extends React.Component {
getSnapshotBeforeUpdate(prevProps, prevState) {
// ...
}
}
此生命周期方法与 componentDidUpdate() 一起涵盖了 componentWillUpdate() 的所有用例。
createElement() 和 cloneElement() 方法有什么区别?
JSX 元素将被转换为 React.createElement() 函数来创建 React 元素,这些对象将用于表示 UI 对象。而 cloneElement 用于克隆元素并传递新的属性。
为什么我们需要将函数传递给 setState() 方法?
这背后的原因是 setState() 是一个异步操作。出于性能原因,React 会对状态更改进行批处理,因此在调用 setState() 方法之后,状态可能不会立即更改。这意味着当你调用 setState() 方法时,你不应该依赖当前状态,因为你不能确定当前状态应该是什么。这个问题的解决方案是将一个函数传递给 setState(),该函数会以上一个状态作为参数。通过这样做,你可以避免由于 setState() 的异步性质而导致用户在访问时获取旧状态值的问题。
假设初始计数值为零。在连续三次增加操作之后,该值将只增加一个。
// assuming this.state.count === 0
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
// this.state.count === 1, not 3
如果将函数传递给 setState(),则 count 将正确递增。
this.setState((prevState, props) => ({
count: prevState.count + props.increment
}))
// this.state.count === 3 as expected
constructor 和 getInitialState 有什么区别?
当使用 ES6 类时,你应该在构造函数中初始化状态,而当你使用 React.createClass() 时,就需要使用 getInitialState() 方法。
使用 ES6 类:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { /* initial state */ }
}
}
如果使用 createReactClass() 方法创建组件,你需要提供一个单独的 getInitialState 方法,让其返回初始 state:
var Counter = createReactClass({
getInitialState: function() {
return {count: this.props.initialCount};
},
// ...
});
是否可以在不调用 setState 方法的情况下,强制组件重新渲染?
默认情况下,当组件的状态或属性改变时,组件将重新渲染。如果你的 render() 方法依赖于其他数据,你可以通过调用 forceUpdate() 来告诉 React,当前组件需要重新渲染。
component.forceUpdate(callback)
建议避免使用 forceUpdate(),并且只在 render() 方法中读取 this.props 和 this.state。
在使用 ES6 类的 React 中 super() 和 super(props) 有什么区别?
当你想要在 constructor() 函数中访问 this.props,你需要将 props 传递给 super() 方法。
使用 super(props):
class MyComponent extends React.Component {
constructor(props) {
super(props)
console.log(this.props) // { name: 'John', ... }
}
}
使用 super():
class MyComponent extends React.Component {
constructor(props) {
super()
console.log(this.props) // undefined
}
}
拓展
React 和 ReactDOM 之间有什么区别?
react 包中包含 React.createElement(), React.Component, React.Children,以及与元素和组件类相关的其他帮助程序。你可以将这些视为构建组件所需的同构或通用帮助程序。react-dom 包中包含了 ReactDOM.render(),在 react-dom/server 包中有支持服务端渲染的 ReactDOMServer.renderToString() 和 ReactDOMServer.renderToStaticMarkup() 方法。
为什么 ReactDOM 从 React 分离出来?
React 团队致力于将所有的与 DOM 相关的特性抽取到一个名为 ReactDOM 的独立库中。React v0.14 是第一个拆分后的版本。通过查看一些软件包,react-native,react-art,react-canvas,和 react-three,很明显,React 的优雅和本质与浏览器或 DOM 无关。为了构建更多 React 能应用的环境,React 团队计划将主要的 React 包拆分成两个:react 和 react-dom。这为编写可以在 React 和 React Native 的 Web 版本之间共享的组件铺平了道路。
如何使用 React label 元素?
如果你尝试使用标准的 for 属性将
<label for={'user'}>{'User'}</label>
<input type={'text'} id={'user'} />
因为 for 是 JavaScript 的保留字,请使用 htmlFor 来替代。
<label htmlFor={'user'}>{'User'}</label>
<input type={'text'} id={'user'} />
如何在调整浏览器大小时重新渲染视图?
你可以在 componentDidMount() 中监听 resize 事件,然后更新尺寸(width 和 height)。你应该在 componentWillUnmount() 方法中移除监听。
class WindowDimensions extends React.Component {
constructor(props) {
super(props);
this.state = {
width: 0,
height: 0,
}
this.updateDimensions()
}
componentDidMount() {
window.addEventListener('resize', this.updateDimensions)
}
componentWillUnmount() {
window.removeEventListener('resize', this.updateDimensions)
}
updateDimensions = () =>{
console.log('resize')
this.setState({ width: window.innerWidth, height: window.innerHeight })
}
render() {
return <span>{this.state.width} x {this.state.height}</span>
}
}
最流行的动画软件包是什么?
React Transition Group 和 React Motion 是React生态系统中流行的动画包。
如何发起 AJAX 调用以及应该在哪些组件生命周期方法中进行 AJAX 调用?
你可以使用 AJAX 库,如 Axios,jQuery AJAX 和浏览器内置的 fetch API。你应该在 componentDidMount() 生命周期方法中获取数据。这样当获取到数据的时候,你就可以使用 setState() 方法来更新你的组件。
React memo 函数是什么?
当类组件的输入属性相同时,可以使用 pureComponent 或 shouldComponentUpdate 来避免组件的渲染。现在,你可以通过把函数组件包装在 React.memo 中来实现相同的功能。
const MyComponent = React.memo(function MyComponent(props) {
/* only rerenders if props change */
});
什么是 hooks?
Hooks 是一个新的草案,它允许你在不编写类的情况下使用状态和其他 React 特性。让我们来看一个 useState 钩子示例:
import { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Hooks 需要遵循什么规则?
为了使用 hooks,你需要遵守两个规则:
-
仅在顶层的 React 函数调用 hooks。也就是说,你不能在循环、条件或内嵌函数中调用 hooks。这将确保每次组件渲染时都以相同的顺序调用 hooks,并且它会在多个 useState 和 useEffect 调用之间保留 hooks 的状态。
-
仅在 React 函数中调用 hooks。例如,你不能在常规的 JavaScript 函数中调用 hooks。
什么是代码拆分?
Code-Splitting 是 Webpack 和 Browserify 等打包工具所支持的一项功能,它可以创建多个 bundles,并可以在运行时动态加载。React 项目支持通过 dynamic import() 特性进行代码拆分。例如,在下面的代码片段中,它将使 moduleA.js 及其所有唯一依赖项作为单独的块,仅当用户点击 ‘Load’ 按钮后才加载。
moduleA.js
const moduleA = 'Hello';
export { moduleA };
App.js
import React, { Component } from 'react';
class App extends Component {
handleClick = () => {
import('./moduleA')
.then(({ moduleA }) => {
// Use moduleA
})
.catch(err => {
// Handle failure
});
};
render() {
return (
<div>
<button onClick={this.handleClick}>Load</button>
</div>
);
}
}
export default App;