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 contextTypestatic defaultPropsstatic 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://: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://: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://: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://: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://: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://: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://: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> ) }