Component
是作为JavaScript 类定义的 React 组件的基类。React 仍然支持类组件,但我们不建议在新代码中使用它们。
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
- 参考
组件
上下文
属性
状态
constructor(props)
componentDidCatch(error, info)
componentDidMount()
componentDidUpdate(prevProps, prevState, snapshot?)
componentWillMount()
componentWillReceiveProps(nextProps)
componentWillUpdate(nextProps, nextState)
componentWillUnmount()
forceUpdate(callback?)
getSnapshotBeforeUpdate(prevProps, prevState)
render()
setState(nextState, callback?)
shouldComponentUpdate(nextProps, nextState, nextContext)
UNSAFE_componentWillMount()
UNSAFE_componentWillReceiveProps(nextProps, nextContext)
UNSAFE_componentWillUpdate(nextProps, nextState)
static contextType
static defaultProps
static getDerivedStateFromError(error)
static getDerivedStateFromProps(props, state)
- 用法
- 替代方案
参考
Component
要将 React 组件定义为类,请扩展内置的Component
类并定义一个render
方法:
import { Component } from 'react';
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
只需要render
方法,其他方法是可选的。
context
类组件的上下文可作为this.context
使用。只有当您使用static contextType
指定要接收的上下文时,它才可用。
类组件一次只能读取一个上下文。
class Button extends Component {
static contextType = ThemeContext;
render() {
const theme = this.context;
const className = 'button-' + theme;
return (
<button className={className}>
{this.props.children}
</button>
);
}
}
props
传递给类组件的属性可作为this.props
使用。
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
<Greeting name="Taylor" />
state
类组件的状态可以通过 this.state
访问。state
字段必须是一个对象。不要直接修改状态。如果要更改状态,请使用新状态调用 setState
。
class Counter extends Component {
state = {
age: 42,
};
handleAgeChange = () => {
this.setState({
age: this.state.age + 1
});
};
render() {
return (
<>
<button onClick={this.handleAgeChange}>
Increment age
</button>
<p>You are {this.state.age}.</p>
</>
);
}
}
constructor(props)
构造函数在类组件 *挂载*(添加到屏幕上)之前运行。通常,在 React 中,构造函数仅用于两种目的。它允许您声明状态并 绑定 类方法到类实例。
class Counter extends Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// ...
}
如果您使用现代 JavaScript 语法,则很少需要构造函数。相反,您可以使用现代浏览器和 Babel 等工具都支持的 公共类字段语法 重写上述代码:
class Counter extends Component {
state = { counter: 0 };
handleClick = () => {
// ...
}
构造函数不应包含任何副作用或订阅。
参数
props
:组件的初始属性。
返回值
constructor
不应返回任何值。
注意事项
-
不要在构造函数中运行任何副作用或订阅。为此,请使用
componentDidMount
。 -
在构造函数中,您需要在任何其他语句之前调用
super(props)
。如果不这样做,在构造函数运行期间this.props
将为undefined
,这可能会令人困惑并导致错误。 -
构造函数是唯一可以直接赋值
this.state
的地方。在所有其他方法中,您需要改用this.setState()
。不要在构造函数中调用setState
。 -
当您使用 服务器端渲染 时,构造函数也会在服务器上运行,然后是
render
方法。但是,生命周期方法(如componentDidMount
或componentWillUnmount
)不会在服务器上运行。 -
当启用 严格模式 时,React 将在开发中两次调用
constructor
,然后丢弃其中一个实例。这有助于您注意到需要从constructor
中移出的意外副作用。
componentDidCatch(error, info)
如果您定义了 componentDidCatch
,则当某些子组件(包括远距离子组件)在渲染过程中抛出错误时,React 将调用它。这允许您在生产环境中将该错误记录到错误报告服务。
通常情况下,它与static getDerivedStateFromError
一起使用,后者允许您响应错误更新状态,并向用户显示错误消息。具有这些方法的组件称为错误边界。
参数
-
error
:抛出的错误。实际上,它通常是Error
的一个实例,但这并非绝对,因为JavaScript允许throw
任何值,包括字符串甚至null
。 -
info
:包含有关错误的其他信息的的对象。它的componentStack
字段包含一个堆栈跟踪,其中包含抛出错误的组件,以及其所有父组件的名称和源位置。在生产环境中,组件名称将被缩小。如果设置了生产错误报告,则可以使用sourcemap解码组件堆栈,就像处理常规JavaScript错误堆栈一样。
返回值
componentDidCatch
不应该返回任何值。
注意事项
-
过去,通常在
componentDidCatch
内部调用setState
以更新UI并显示回退错误消息。这已被弃用,取而代之的是定义static getDerivedStateFromError
。 -
React 的生产和开发版本在
componentDidCatch
处理错误的方式上略有不同。在开发环境中,错误将冒泡到window
,这意味着任何window.onerror
或window.addEventListener('error', callback)
都将拦截componentDidCatch
捕获的错误。相反,在生产环境中,错误不会冒泡,这意味着任何祖先错误处理程序只会接收componentDidCatch
未显式捕获的错误。
componentDidMount()
如果您定义了componentDidMount
方法,React将在您的组件添加到(挂载到)屏幕时调用它。这是开始数据获取、设置订阅或操作DOM节点的常见位置。
如果您实现了componentDidMount
,您通常需要实现其他生命周期方法以避免错误。例如,如果componentDidMount
读取某些状态或属性,您还必须实现componentDidUpdate
以处理它们的更改,并实现componentWillUnmount
以清理componentDidMount
正在执行的操作。
class ChatRoom extends Component {
state = {
serverUrl: 'https://127.0.0.1:1234'
};
componentDidMount() {
this.setupConnection();
}
componentDidUpdate(prevProps, prevState) {
if (
this.props.roomId !== prevProps.roomId ||
this.state.serverUrl !== prevState.serverUrl
) {
this.destroyConnection();
this.setupConnection();
}
}
componentWillUnmount() {
this.destroyConnection();
}
// ...
}
参数
componentDidMount
不接受任何参数。
返回值
componentDidMount
不应该返回任何值。
注意事项
-
当启用严格模式时,在开发环境中,React 会调用
componentDidMount
,然后立即调用componentWillUnmount
,之后再次调用componentDidMount
。这有助于您发现是否忘记实现componentWillUnmount
,或者其逻辑是否没有完全“镜像”componentDidMount
的功能。 -
虽然您可以在
componentDidMount
中立即调用setState
,但如果可以,最好避免这样做。它会触发额外的渲染,但这会在浏览器更新屏幕之前发生。这保证了即使render
在这种情况下会被调用两次,用户也不会看到中间状态。谨慎使用此模式,因为它常常会导致性能问题。在大多数情况下,您应该能够在constructor
中分配初始状态。但是,对于模态框和工具提示等情况,当您需要在渲染依赖于其大小或位置的内容之前测量 DOM 节点时,这可能是必要的。
componentDidUpdate(prevProps, prevState, snapshot?)
如果您定义了componentDidUpdate
方法,则在组件使用更新后的 props 或 state 重新渲染后,React 会立即调用它。此方法不会在初始渲染时调用。
您可以使用它在更新后操作 DOM。只要将当前 props 与之前的 props 进行比较(例如,如果 props 没有更改,则可能不需要网络请求),这也是进行网络请求的常见位置。通常,您会将它与componentDidMount
和componentWillUnmount
一起使用:
class ChatRoom extends Component {
state = {
serverUrl: 'https://127.0.0.1:1234'
};
componentDidMount() {
this.setupConnection();
}
componentDidUpdate(prevProps, prevState) {
if (
this.props.roomId !== prevProps.roomId ||
this.state.serverUrl !== prevState.serverUrl
) {
this.destroyConnection();
this.setupConnection();
}
}
componentWillUnmount() {
this.destroyConnection();
}
// ...
}
参数
-
prevProps
:更新前的 Props。比较prevProps
和this.props
以确定发生了哪些更改。 -
prevState
:更新前的 State。比较prevState
和this.state
以确定发生了哪些更改。 -
snapshot
:如果您实现了getSnapshotBeforeUpdate
,则snapshot
将包含您从该方法返回的值。否则,它将为undefined
。
返回值
componentDidUpdate
不应该返回任何内容。
注意事项
-
如果定义了
shouldComponentUpdate
并返回false
,则不会调用componentDidUpdate
。 -
componentDidUpdate
内部的逻辑通常应该用条件语句包装起来,这些条件语句比较this.props
和prevProps
,以及this.state
和prevState
。否则,存在创建无限循环的风险。 -
虽然你可以在
componentDidUpdate
中立即调用setState
,但最好尽量避免这种情况。它会触发额外的渲染,但这会在浏览器更新屏幕之前发生。这保证了即使在这种情况下render
会被调用两次,用户也不会看到中间状态。这种模式经常导致性能问题,但在某些罕见情况下(例如模态框和工具提示,你需要在渲染依赖于其大小或位置的内容之前测量 DOM 节点)可能是必要的。
componentWillMount()
componentWillReceiveProps(nextProps)
componentWillUpdate(nextProps, nextState)
componentWillUnmount()
如果你定义了 componentWillUnmount
方法,React 将在你的组件从屏幕中移除(卸载)之前调用它。这是一个取消数据获取或移除订阅的常见位置。
componentWillUnmount
内部的逻辑应该“镜像” componentDidMount
内部的逻辑。例如,如果 componentDidMount
设置了一个订阅,componentWillUnmount
应该清理该订阅。如果 componentWillUnmount
中的清理逻辑读取了一些 props 或 state,你通常也需要实现 componentDidUpdate
来清理与旧 props 和 state 相对应的资源(例如订阅)。
class ChatRoom extends Component {
state = {
serverUrl: 'https://127.0.0.1:1234'
};
componentDidMount() {
this.setupConnection();
}
componentDidUpdate(prevProps, prevState) {
if (
this.props.roomId !== prevProps.roomId ||
this.state.serverUrl !== prevState.serverUrl
) {
this.destroyConnection();
this.setupConnection();
}
}
componentWillUnmount() {
this.destroyConnection();
}
// ...
}
参数
componentWillUnmount
不接受任何参数。
返回值
componentWillUnmount
不应返回任何内容。
注意事项
- 当启用 严格模式 (Strict Mode) 时,在开发环境中,React 将会调用
componentDidMount
,然后立即调用componentWillUnmount
,之后再次调用componentDidMount
。这有助于你发现是否忘记实现componentWillUnmount
,或者其逻辑是否完全“镜像”了componentDidMount
的功能。
forceUpdate(callback?)
强制组件重新渲染。
通常情况下,这并非必要。如果你的组件的 render
方法只读取 this.props
、this.state
或 this.context
,那么当你调用组件内部或其父组件中的 setState
时,它会自动重新渲染。但是,如果你的组件的 render
方法直接读取外部数据源,则你必须告诉 React 在该数据源更改时更新用户界面。这就是 forceUpdate
允许你做的事情。
尽量避免使用 forceUpdate
,并且在 render
中只读取 this.props
和 this.state
。
参数
- 可选
callback
如果指定,React 将会在更新提交后调用你提供的callback
。
返回值
forceUpdate
不返回任何值。
注意事项
- 如果你调用
forceUpdate
,React 将会重新渲染而不会调用shouldComponentUpdate
。
getSnapshotBeforeUpdate(prevProps, prevState)
如果你实现了 getSnapshotBeforeUpdate
,React 将会在 React 更新 DOM 之前立即调用它。它使你的组件能够在 DOM 潜在更改之前捕获 DOM 中的一些信息(例如滚动位置)。此生命周期方法返回的任何值都将作为参数传递给 componentDidUpdate
。
例如,你可以在像聊天线程这样的 UI 中使用它,该 UI 需要在更新期间保留其滚动位置。
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the scroll position so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
在上面的示例中,直接在 getSnapshotBeforeUpdate
中读取 scrollHeight
属性非常重要。在 render
、UNSAFE_componentWillReceiveProps
或 UNSAFE_componentWillUpdate
中读取它是不安全的,因为这些方法被调用与 React 更新 DOM 之间存在潜在的时间差。
参数
-
prevProps
:更新前的 Props。比较prevProps
和this.props
以确定发生了哪些更改。 -
prevState
:更新前的 State。比较prevState
和this.state
以确定发生了哪些更改。
返回值
你应该返回任何你想要的类型的快照值,或者 null
。你返回的值将作为第三个参数传递给 componentDidUpdate
。
注意事项
getSnapshotBeforeUpdate
如果shouldComponentUpdate
已定义并返回false
,则不会被调用。
render()
在类组件中,render
方法是唯一必需的方法。
render
方法应该指定你想在屏幕上显示的内容,例如
import { Component } from 'react';
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
React 可以在任何时刻调用 render
,所以你不应该假设它在特定时间运行。通常,render
方法应该返回一段 JSX,但也支持一些 其他的返回类型(如字符串)。为了计算返回的 JSX,render
方法可以读取 this.props
、this.state
和 this.context
。
你应该将 render
方法编写为纯函数,这意味着如果 props、state 和 context 相同,它应该返回相同的结果。它也不应该包含副作用(例如设置订阅)或与浏览器 API 交互。副作用应该发生在事件处理程序或 componentDidMount
等方法中。
参数
render
不接受任何参数。
返回值
render
可以返回任何有效的 React 节点。这包括 React 元素,例如 <div />
,字符串,数字,portals,空节点(null
,undefined
,true
和 false
)以及 React 节点的数组。
注意事项
-
render
应该编写为 props、state 和 context 的纯函数。它不应该有副作用。 -
render
如果shouldComponentUpdate
已定义并返回false
,则不会被调用。 -
当 严格模式 启用时,React 会在开发过程中调用
render
两次,然后丢弃其中一个结果。这有助于你注意到需要从render
方法中移出的意外副作用。 -
render
调用与其后的componentDidMount
或componentDidUpdate
调用之间并非一一对应关系。当 React 认为这样做有利时,它可能会丢弃一些render
调用的结果。
setState(nextState, callback?)
调用 setState
来更新 React 组件的状态。
class Form extends Component {
state = {
name: 'Taylor',
};
handleNameChange = (e) => {
const newName = e.target.value;
this.setState({
name: newName
});
}
render() {
return (
<>
<input value={this.state.name} onChange={this.handleNameChange} />
<p>Hello, {this.state.name}.</p>
</>
);
}
}
setState
将组件状态的更改排队。它告诉 React,这个组件及其子组件需要使用新状态重新渲染。这是响应交互更新用户界面的主要方法。
您也可以将函数传递给 setState
。它允许您根据先前状态更新状态。
handleIncreaseAge = () => {
this.setState(prevState => {
return {
age: prevState.age + 1
};
});
}
您不必这样做,但是如果您想在同一事件中多次更新状态,则此方法非常方便。
参数
-
nextState
:对象或函数。- 如果将对象作为
nextState
传递,它将被浅合并到this.state
中。 - 如果将函数作为
nextState
传递,它将被视为更新器函数。它必须是纯函数,应该以挂起的 state 和 props 作为参数,并应该返回要浅合并到this.state
中的对象。React 将把您的更新器函数放入队列中并重新渲染您的组件。在下一个渲染期间,React 将通过将所有排队的更新器应用于先前状态来计算下一个状态。
- 如果将对象作为
-
可选
callback
:如果指定,则在更新提交后,React 将调用您提供的callback
。
返回值
setState
不返回任何值。
注意事项
-
将
setState
视为请求,而不是立即更新组件的命令。当多个组件响应事件更新其状态时,React 将批量处理它们的更新并在事件结束时一次性重新渲染它们。在极少数情况下,如果您需要强制同步应用特定状态更新,您可以将其包装在flushSync
中,但这可能会影响性能。 -
setState
不会立即更新this.state
。这使得在调用setState
后立即读取this.state
成为一个潜在的陷阱。相反,请使用componentDidUpdate
或setState
的callback
参数,这两者都保证在应用更新后触发。如果您需要根据先前状态设置状态,则可以像上面描述的那样将函数传递给nextState
。
shouldComponentUpdate(nextProps, nextState, nextContext)
如果您定义了 shouldComponentUpdate
,React 将调用它来确定是否可以跳过重新渲染。
如果您确定要手动编写,可以比较this.props
和nextProps
,以及this.state
和nextState
,并返回false
来告诉 React 可以跳过更新。
class Rectangle extends Component {
state = {
isHovered: false
};
shouldComponentUpdate(nextProps, nextState) {
if (
nextProps.position.x === this.props.position.x &&
nextProps.position.y === this.props.position.y &&
nextProps.size.width === this.props.size.width &&
nextProps.size.height === this.props.size.height &&
nextState.isHovered === this.state.isHovered
) {
// Nothing has changed, so a re-render is unnecessary
return false;
}
return true;
}
// ...
}
当接收到新的 props 或 state 时,React 会在渲染之前调用shouldComponentUpdate
。默认为true
。此方法不会在初始渲染或使用forceUpdate
时调用。
参数
nextProps
:组件即将使用的下一个 props。比较nextProps
和this.props
来确定发生了哪些变化。nextState
:组件即将使用的下一个 state。比较nextState
和this.state
来确定发生了哪些变化。nextContext
:组件即将使用的下一个 context。比较nextContext
和this.context
来确定发生了哪些变化。只有在您指定static contextType
时才可用。
返回值
如果您希望组件重新渲染,则返回true
。这是默认行为。
返回false
以告诉 React 可以跳过重新渲染。
注意事项
-
此方法*仅*作为性能优化存在。如果您的组件没有它就无法工作,请首先修复它。
-
考虑使用
PureComponent
,而不是手动编写shouldComponentUpdate
。PureComponent
会浅比较 props 和 state,并减少跳过必要更新的可能性。 -
我们不建议在
shouldComponentUpdate
中进行深度相等性检查或使用JSON.stringify
。这会使性能变得不可预测,并取决于每个 prop 和 state 的数据结构。最好的情况下,您可能会导致应用程序出现数秒的停顿,最坏的情况下可能会导致应用程序崩溃。 -
返回
false
不会阻止子组件在*其*状态发生变化时重新渲染。 -
返回
false
并*不能*保证组件不会重新渲染。React 会将返回值用作提示,但如果出于其他原因重新渲染组件是有意义的,它仍然可能会选择重新渲染您的组件。
UNSAFE_componentWillMount()
如果您定义了UNSAFE_componentWillMount
,React 将在constructor
之后立即调用它。它只出于历史原因存在,不应在任何新代码中使用。请改用其中一种替代方法。
- 要初始化 state,请将
state
声明为类字段,或在constructor
内部设置this.state
。 - 如果您需要运行副作用或设置订阅,请将该逻辑移至
componentDidMount
。
参数
UNSAFE_componentWillMount
不接受任何参数。
返回值
UNSAFE_componentWillMount
不应该返回任何值。
注意事项
-
尽管名称如此,如果组件实现了
static getDerivedStateFromProps
或getSnapshotBeforeUpdate
,UNSAFE_componentWillMount
不会被调用。 -
尽管其名称如此,
UNSAFE_componentWillMount
并不能保证组件一定会被挂载,尤其是在你的应用使用了现代 React 特性,例如Suspense
。如果渲染尝试被挂起(例如,因为某些子组件的代码尚未加载),React 将丢弃正在进行的树,并在下一次尝试期间尝试从头开始构建组件。这就是为什么此方法“不安全”。依赖于挂载的代码(例如添加订阅)应该放在componentDidMount
中。 -
UNSAFE_componentWillMount
是唯一在 服务器端渲染 期间运行的生命周期方法。实际上,它与constructor
完全相同,因此你应该改用constructor
来实现这种类型的逻辑。
UNSAFE_componentWillReceiveProps(nextProps, nextContext)
如果你定义了 UNSAFE_componentWillReceiveProps
,当组件接收到新的 props 时,React 将调用它。它只出于历史原因而存在,不应该在新代码中使用。请改用以下替代方案。
- 如果你需要运行副作用(例如,获取数据、运行动画或重新初始化订阅),请将该逻辑移动到
componentDidUpdate
。 - 如果你需要仅在 prop 更改时避免重新计算某些数据,请改用 记忆化辅助函数。
- 如果你需要在 prop 更改时“重置”某些状态,请考虑将组件设为 完全受控的 或 完全不受控的(带 key)。
- 如果你需要在 prop 更改时“调整”某些状态,请检查是否可以在渲染期间仅从 props 中计算所有必要信息。如果不能,请改用
static getDerivedStateFromProps
。
参数
nextProps
:组件即将从其父组件接收的下一个 props。比较nextProps
和this.props
以确定发生了哪些更改。nextContext
:组件即将从最近的提供者接收的下一个上下文。比较nextContext
和this.context
以确定发生了哪些更改。仅当您指定static contextType
时才可用。
返回值
UNSAFE_componentWillReceiveProps
不应返回任何值。
注意事项
-
如果组件实现了
static getDerivedStateFromProps
或getSnapshotBeforeUpdate
,则不会调用UNSAFE_componentWillReceiveProps
。 -
尽管名称如此,
UNSAFE_componentWillReceiveProps
并不能保证组件一定会接收到这些props,尤其当你的应用使用了现代React特性例如Suspense
。如果渲染尝试被挂起(例如,因为某个子组件的代码尚未加载),React 将丢弃正在进行的树,并在下一次尝试期间尝试从头开始构建组件。在下一次渲染尝试时,props 可能已经不同了。这就是为什么此方法“不安全”。仅应为已提交的更新运行的代码(例如重置订阅)应放入componentDidUpdate
。 -
UNSAFE_componentWillReceiveProps
并不能表示组件收到了与上次不同的props。你需要自己比较nextProps
和this.props
来检查是否有任何变化。 -
React 在挂载期间不会使用初始props调用
UNSAFE_componentWillReceiveProps
。只有当组件的某些props即将更新时,才会调用此方法。例如,调用setState
通常不会触发同一组件内的UNSAFE_componentWillReceiveProps
。
UNSAFE_componentWillUpdate(nextProps, nextState)
如果你定义了UNSAFE_componentWillUpdate
,React 将在使用新的 props 或 state 渲染之前调用它。它仅出于历史原因存在,不应在任何新代码中使用。请改用其中一种替代方法。
- 如果你需要响应 prop 或 state 的变化运行副作用(例如,获取数据、运行动画或重新初始化订阅),请将该逻辑移至
componentDidUpdate
。 - 如果你需要从DOM读取一些信息(例如,保存当前滚动位置),以便稍后在
componentDidUpdate
中使用它,请改在getSnapshotBeforeUpdate
中读取它。
参数
nextProps
:组件即将使用的下一个 props。比较nextProps
和this.props
来确定发生了哪些变化。nextState
:组件即将渲染的下一个状态。比较nextState
和this.state
以确定发生了哪些变化。
返回值
UNSAFE_componentWillUpdate
不应返回任何值。
注意事项
-
如果定义了
shouldComponentUpdate
并返回false
,则不会调用UNSAFE_componentWillUpdate
。 -
如果组件实现了
static getDerivedStateFromProps
或getSnapshotBeforeUpdate
,则不会调用UNSAFE_componentWillUpdate
。 -
在
componentWillUpdate
期间,不支持调用setState
(或任何导致调用setState
的方法,例如分发Redux action)。 -
尽管名称如此,如果你的应用使用了现代React特性,例如
Suspense
,UNSAFE_componentWillUpdate
并不能保证组件一定会更新。如果渲染尝试被挂起(例如,因为某些子组件的代码尚未加载),React将丢弃正在进行的树,并在下次尝试时尝试从头开始构建组件。在下一次渲染尝试时,props和state可能已经不同了。这就是为什么此方法“不安全”。应该仅针对已提交的更新运行的代码(例如重置订阅)应该放在componentDidUpdate
中。 -
UNSAFE_componentWillUpdate
并不意味着组件收到了与上次不同的props或state。你需要自己比较nextProps
和this.props
以及nextState
和this.state
来检查是否有更改。 -
React在挂载过程中不会使用初始props和state调用
UNSAFE_componentWillUpdate
。
static contextType
如果要从类组件中读取this.context
,必须指定它需要读取哪个上下文。作为static contextType
指定的上下文必须是之前由createContext
创建的值。
class Button extends Component {
static contextType = ThemeContext;
render() {
const theme = this.context;
const className = 'button-' + theme;
return (
<button className={className}>
{this.props.children}
</button>
);
}
}
static defaultProps
可以定义static defaultProps
来为类设置默认props。它们将用于undefined
和缺少的props,但不适用于null
props。
例如,以下是定义color
prop 默认为'blue'
的方法。
class Button extends Component {
static defaultProps = {
color: 'blue'
};
render() {
return <button className={this.props.color}>click me</button>;
}
}
如果没有提供color
prop 或其值为undefined
,它将默认设置为'blue'
。
<>
{/* this.props.color is "blue" */}
<Button />
{/* this.props.color is "blue" */}
<Button color={undefined} />
{/* this.props.color is null */}
<Button color={null} />
{/* this.props.color is "red" */}
<Button color="red" />
</>
static getDerivedStateFromError(error)
如果定义了static getDerivedStateFromError
,则当子组件(包括远端子组件)在渲染期间抛出错误时,React将调用它。这允许你显示错误消息,而不是清除UI。
通常情况下,它与componentDidCatch
一起使用,后者允许你将错误报告发送到某些分析服务。具有这些方法的组件称为错误边界。
参数
返回值
static getDerivedStateFromError
应该返回指示组件显示错误消息的状态。
注意事项
static getDerivedStateFromError
应该是一个纯函数。如果您想执行副作用(例如,调用分析服务),则还需要实现componentDidCatch
。
static getDerivedStateFromProps(props, state)
如果您定义了 static getDerivedStateFromProps
,React 将在调用 render
之前调用它,在初始挂载和后续更新时都是如此。它应该返回一个对象来更新状态,或者返回 null
来表示不更新任何内容。
此方法适用于 少数几种情况,其中状态取决于一段时间内 props 的变化。例如,此 Form
组件在 userID
prop 发生变化时重置 email
状态。
class Form extends Component {
state = {
email: this.props.defaultEmail,
prevUserID: this.props.userID
};
static getDerivedStateFromProps(props, state) {
// Any time the current user changes,
// Reset any parts of state that are tied to that user.
// In this simple example, that's just the email.
if (props.userID !== state.prevUserID) {
return {
prevUserID: props.userID,
email: props.defaultEmail
};
}
return null;
}
// ...
}
请注意,此模式要求您在状态中保留 prop 的先前值(如 userID
)(如 prevUserID
)。
参数
props
:组件即将呈现的下一个 props。state
:组件即将呈现的下一个 state。
返回值
static getDerivedStateFromProps
返回一个对象来更新状态,或者返回 null
来表示不更新任何内容。
注意事项
-
此方法在每次渲染时都会触发,无论原因如何。这与
UNSAFE_componentWillReceiveProps
不同,后者仅在父组件导致重新渲染时才会触发,而不会因本地setState
而触发。 -
此方法无法访问组件实例。如果您愿意,可以通过在类定义之外提取组件 props 和 state 的纯函数,在
static getDerivedStateFromProps
和其他类方法之间重用一些代码。
使用
定义类组件
要将 React 组件定义为类,请扩展内置的Component
类并定义一个render
方法:
import { Component } from 'react';
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
React 需要渲染屏幕内容时,会调用你的 render
方法。通常情况下,你会从中返回一些 JSX。你的 render
方法应该是一个 纯函数:它只应该计算 JSX。
与 函数组件 类似,类组件可以从父组件通过 props 接收信息。但是,读取 props 的语法有所不同。例如,如果父组件渲染 <Greeting name="Taylor" />
,那么你可以从 this.props
读取 name
prop,例如 this.props.name
import { Component } from 'react'; class Greeting extends Component { render() { return <h1>Hello, {this.props.name}!</h1>; } } export default function App() { return ( <> <Greeting name="Sara" /> <Greeting name="Cahal" /> <Greeting name="Edite" /> </> ); }
注意,Hooks(以 use
开头的函数,例如 useState
)在类组件中不受支持。
向类组件添加状态
要向类添加 状态,请将一个对象赋值给名为 state
的属性。要更新状态,请调用 this.setState
。
import { Component } from 'react'; export default class Counter extends Component { state = { name: 'Taylor', age: 42, }; handleNameChange = (e) => { this.setState({ name: e.target.value }); } handleAgeChange = () => { this.setState({ age: this.state.age + 1 }); }; render() { return ( <> <input value={this.state.name} onChange={this.handleNameChange} /> <button onClick={this.handleAgeChange}> Increment age </button> <p>Hello, {this.state.name}. You are {this.state.age}.</p> </> ); } }
向类组件添加生命周期方法
你可以在你的类中定义一些特殊的方法。
如果你定义了 componentDidMount
方法,当你的组件添加到屏幕上(挂载)时,React 将会调用它。当你的组件由于 props 或状态改变而重新渲染后,React 将会调用 componentDidUpdate
。当你的组件从屏幕上移除(卸载)后,React 将会调用 componentWillUnmount
。
如果你实现了 componentDidMount
,你通常需要实现所有三个生命周期方法以避免错误。例如,如果 componentDidMount
读取了一些状态或 props,你也必须实现 componentDidUpdate
来处理它们的更改,并实现 componentWillUnmount
来清理 componentDidMount
所做的任何事情。
例如,这个 ChatRoom
组件保持与 props 和状态同步的聊天连接
import { Component } from 'react'; import { createConnection } from './chat.js'; export default class ChatRoom extends Component { state = { serverUrl: 'https://127.0.0.1:1234' }; componentDidMount() { this.setupConnection(); } componentDidUpdate(prevProps, prevState) { if ( this.props.roomId !== prevProps.roomId || this.state.serverUrl !== prevState.serverUrl ) { this.destroyConnection(); this.setupConnection(); } } componentWillUnmount() { this.destroyConnection(); } setupConnection() { this.connection = createConnection( this.state.serverUrl, this.props.roomId ); this.connection.connect(); } destroyConnection() { this.connection.disconnect(); this.connection = null; } render() { return ( <> <label> Server URL:{' '} <input value={this.state.serverUrl} onChange={e => { this.setState({ serverUrl: e.target.value }); }} /> </label> <h1>Welcome to the {this.props.roomId} room!</h1> </> ); } }
注意,在开发模式下,如果启用了 严格模式,React 将会调用 componentDidMount
,立即调用 componentWillUnmount
,然后再次调用 componentDidMount
。这有助于你注意到是否忘记了实现 componentWillUnmount
,或者它的逻辑是否没有完全“镜像” componentDidMount
的操作。
使用错误边界捕获渲染错误
默认情况下,如果您的应用程序在渲染过程中抛出错误,React 将从屏幕中移除其 UI。为了防止这种情况,您可以将 UI 的一部分包装到一个错误边界中。错误边界是一个特殊的组件,它允许您显示一些备用 UI 来代替崩溃的部分——例如,一条错误消息。
要实现错误边界组件,您需要提供 static getDerivedStateFromError
,它允许您根据错误更新状态并向用户显示错误消息。您还可以选择实现 componentDidCatch
来添加一些额外的逻辑,例如将错误记录到分析服务。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, info) {
// Example "componentStack":
// in ComponentThatThrows (created by App)
// in ErrorBoundary (created by App)
// in div (created by App)
// in App
logErrorToMyService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return this.props.fallback;
}
return this.props.children;
}
}
然后您可以用它来包装组件树的一部分。
<ErrorBoundary fallback={<p>Something went wrong</p>}>
<Profile />
</ErrorBoundary>
如果 Profile
或其子组件抛出错误,ErrorBoundary
将“捕获”该错误,显示带有您提供的错误消息的备用 UI,并将生产错误报告发送到您的错误报告服务。
您不需要将每个组件都包装到单独的错误边界中。当您考虑错误边界的粒度时,请考虑在何处显示错误消息最合适。例如,在消息应用程序中,将错误边界放在对话列表周围是合理的。将其放在每个单独的消息周围也是合理的。但是,将边界放在每个头像周围就没有意义了。
替代方案
将简单的类组件迁移到函数组件
通常,您将将组件定义为函数。
例如,假设您正在将这个 Greeting
类组件转换为函数
import { Component } from 'react'; class Greeting extends Component { render() { return <h1>Hello, {this.props.name}!</h1>; } } export default function App() { return ( <> <Greeting name="Sara" /> <Greeting name="Cahal" /> <Greeting name="Edite" /> </> ); }
定义一个名为 Greeting
的函数。您将在此处移动 render
函数的主体。
function Greeting() {
// ... move the code from the render method here ...
}
代替 this.props.name
,使用解构语法定义 name
prop 并直接读取它
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
这是一个完整的示例
function Greeting({ name }) { return <h1>Hello, {name}!</h1>; } export default function App() { return ( <> <Greeting name="Sara" /> <Greeting name="Cahal" /> <Greeting name="Edite" /> </> ); }
import { Component } from 'react'; export default class Counter extends Component { state = { name: 'Taylor', age: 42, }; handleNameChange = (e) => { this.setState({ name: e.target.value }); } handleAgeChange = (e) => { this.setState({ age: this.state.age + 1 }); }; render() { return ( <> <input value={this.state.name} onChange={this.handleNameChange} /> <button onClick={this.handleAgeChange}> Increment age </button> <p>Hello, {this.state.name}. You are {this.state.age}.</p> </> ); } }
首先声明一个具有必要的状态变量的函数:
import { useState } from 'react';
function Counter() {
const [name, setName] = useState('Taylor');
const [age, setAge] = useState(42);
// ...
接下来,转换事件处理程序
function Counter() {
const [name, setName] = useState('Taylor');
const [age, setAge] = useState(42);
function handleNameChange(e) {
setName(e.target.value);
}
function handleAgeChange() {
setAge(age + 1);
}
// ...
最后,将所有以 this
开头的引用替换为您在组件中定义的变量和函数。例如,将 this.state.age
替换为 age
,并将 this.handleNameChange
替换为 handleNameChange
。
这是一个完全转换后的组件
import { useState } from 'react'; export default function Counter() { const [name, setName] = useState('Taylor'); const [age, setAge] = useState(42); function handleNameChange(e) { setName(e.target.value); } function handleAgeChange() { setAge(age + 1); } return ( <> <input value={name} onChange={handleNameChange} /> <button onClick={handleAgeChange}> Increment age </button> <p>Hello, {name}. You are {age}.</p> </> ) }
import { Component } from 'react'; import { createConnection } from './chat.js'; export default class ChatRoom extends Component { state = { serverUrl: 'https://127.0.0.1:1234' }; componentDidMount() { this.setupConnection(); } componentDidUpdate(prevProps, prevState) { if ( this.props.roomId !== prevProps.roomId || this.state.serverUrl !== prevState.serverUrl ) { this.destroyConnection(); this.setupConnection(); } } componentWillUnmount() { this.destroyConnection(); } setupConnection() { this.connection = createConnection( this.state.serverUrl, this.props.roomId ); this.connection.connect(); } destroyConnection() { this.connection.disconnect(); this.connection = null; } render() { return ( <> <label> Server URL:{' '} <input value={this.state.serverUrl} onChange={e => { this.setState({ serverUrl: e.target.value }); }} /> </label> <h1>Welcome to the {this.props.roomId} room!</h1> </> ); } }
首先,验证您的componentWillUnmount
是否与componentDidMount
执行相反的操作。在上面的示例中,这是正确的:它断开了componentDidMount
建立的连接。如果缺少此类逻辑,请先添加。
接下来,验证您的componentDidUpdate
方法是否处理您在componentDidMount
中使用的任何 props 和 state 的更改。在上面的示例中,componentDidMount
调用setupConnection
,它读取this.state.serverUrl
和this.props.roomId
。这就是为什么componentDidUpdate
检查this.state.serverUrl
和this.props.roomId
是否已更改,如果已更改则重置连接。如果您的componentDidUpdate
逻辑缺失或未处理所有相关 props 和 state 的更改,请先修复。
在上面的示例中,生命周期方法内的逻辑将组件连接到 React 系统外部(聊天服务器)。要将组件连接到外部系统,请将此逻辑描述为单个 Effect:
import { useState, useEffect } from 'react';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://127.0.0.1:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
此useEffect
调用等效于上述生命周期方法中的逻辑。如果您的生命周期方法执行多个不相关的操作,请将它们拆分为多个独立的 Effect。 这是一个您可以使用的完整示例
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; export default function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://127.0.0.1:1234'); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { connection.disconnect(); }; }, [roomId, serverUrl]); return ( <> <label> Server URL:{' '} <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /> </label> <h1>Welcome to the {roomId} room!</h1> </> ); }
import { createContext, Component } from 'react'; const ThemeContext = createContext(null); class Panel extends Component { static contextType = ThemeContext; render() { const theme = this.context; const className = 'panel-' + theme; return ( <section className={className}> <h1>{this.props.title}</h1> {this.props.children} </section> ); } } class Button extends Component { static contextType = ThemeContext; render() { const theme = this.context; const className = 'button-' + theme; return ( <button className={className}> {this.props.children} </button> ); } } function Form() { return ( <Panel title="Welcome"> <Button>Sign up</Button> <Button>Log in</Button> </Panel> ); } export default function MyApp() { return ( <ThemeContext.Provider value="dark"> <Form /> </ThemeContext.Provider> ) }
当您将它们转换为函数组件时,请将this.context
替换为useContext
调用。
import { createContext, useContext } from 'react'; const ThemeContext = createContext(null); function Panel({ title, children }) { const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( <section className={className}> <h1>{title}</h1> {children} </section> ) } function Button({ children }) { const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( <button className={className}> {children} </button> ); } function Form() { return ( <Panel title="Welcome"> <Button>Sign up</Button> <Button>Log in</Button> </Panel> ); } export default function MyApp() { return ( <ThemeContext.Provider value="dark"> <Form /> </ThemeContext.Provider> ) }