陷阱

我们建议使用函数式组件,而不是类式组件。了解如何迁移

Component 是一个 JavaScript 类,其被定义为 React 组件的基类,类式组件仍然被 React 支持,但我们不建议在新代码中使用它们。

class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}

参考

Component

如果想要使用类式组件,需要继承内置的 Component 类并定义 render 方法

import { Component } from 'react';

class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}

只有 render 方法是必要的,其他方法是可选的。

参见下方更多示例


context

类式组件可以通过使用 this.context 访问 context。但是只有使用 static contextType 指定想要接受 哪一个 context 时才会有效。

类式组件一次只能读取一个 context。

class Button extends Component {
static contextType = ThemeContext;

render() {
const theme = this.context;
const className = 'button-' + theme;
return (
<button className={className}>
{this.props.children}
</button>
);
}
}

注意

在类式组件中读取 this.context 等同于在函数式组件中使用 useContext

了解如何迁移


props

使用 this.props 能够访问一个类组件的 props。

class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}

<Greeting name="Taylor" />

注意

在类式组件中读取 this.props 等同于在函数式组件中使用 声明式 props

了解如何迁移


state

使用 this.state 来访问一个类式组件的 state。state 字段必须是一个对象。请不要直接改变 state 的值。如果你希望改变 state,那么请使用新的 state 来调用 setState 函数。

class Counter extends Component {
state = {
age: 42,
};

handleAgeChange = () => {
this.setState({
age: this.state.age + 1
});
};

render() {
return (
<>
<button onClick={this.handleAgeChange}>
增加年龄
</button>
<p>{this.state.age}岁了。</p>
</>
);
}
}

注意

在类式组件中定义 state 等同于在函数式组件中调用 useState

了解如何迁移


constructor(props)

constructor 会在你的类式组件 挂载(添加到屏幕上)之前运行。一般来说,在 React 中 constructor 仅用于两个目的。它可以让你来声明 state 以及将你的类方法 绑定 到你的类实例上。

class Counter extends Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
// ...
}

如果你使用较新的 JavaScript 语法的话,那么很少需要使用到 constructors。相反,你可以使用现代浏览器和像 Babel 这样的工具都支持的 公有类字段语法 来重写上面的代码:

class Counter extends Component {
state = { counter: 0 };

handleClick = () => {
// ...
}

构造函数不应包含任何存在副作用或者事件监听相关的代码。

参数

  • props:组件初始的 props。

返回值

constructor 不应该返回任何值。

注意

  • 不要在 constructor 中运行任何副作用或者监听相关的代码。使用 componentDidMount 来应对这种需求。

  • 在 constructor 中,你需要在其他的声明之前调用 super(props)。如果你不这样做,this.props 在 constructor 运行时就会为 undefined,这可能会造成困惑并且导致错误。

  • Constructor 中是唯一一个你能直接赋值 this.state 的地方。在其余所有方法中,你需要使用 this.setState() 来代替。不要在 constructor 中使用 setState

  • 当你使用 服务端渲染 时,constructor 也将在服务端运行,然后紧接着会运行 render 方法。然而,像 componentDidMount 或者 componentWillUnmount 这样的生命周期方法将不会在服务端运行。

  • 严格模式 打开时,React 将会在开发过程中调用两次 constructor 然后丢弃其中的一个实例。这有助于你注意到需要从 constructor 中移出的意外的副作用。

注意

在函数式组件中没有与 constructor 作用完全相同的函数。要在函数式组件中声明 state 请调用 useState 来避免重新计算初始的 state,传递一个函数给 useState


componentDidCatch(error, info)

如果你定义了 componentDidCatch,那么 React 将在某些子组件(包括后代组件)在渲染过程中抛出错误时调用它。这使得你可以在生产中将该错误记录到错误报告服务中。

一般来说,它与 static getDerivedStateFromError 一起使用,这样做允许你更新 state 来响应错误并向用户显示错误消息。具有这些方法的组件称为 错误边界

查看示例

参数

  • error:被抛出的错误。实际上,它通常会是一个 Error 的实例,但是这并不能保证,因为 JavaScript 允许 抛出 所有值,包括字符串甚至是 null

  • info:一个包含有关错误的附加信息的对象。它的 componentStack 字段包含一个关于有关组件的堆栈跟踪,以及其所有父组件的名称和源位置。在生产中,组件名称将被简化。如果你设置了生产错误报告服务,则可以使用源映射来解码组件堆栈,就像处理常规 JavaScript 错误堆栈一样。

返回值

componentDidCatch 不应该返回任何值。

注意

  • 在以前经常会在 componentDidCatch 中使用 setState 来更新 UI 以及显示后备错误消息。我们反对这种方法,更赞同定义 static getDerivedStateFromError

  • componentDidCatch 在 React 生产环境和开发环境中处理错误的方式有所不同,在开发环境下,错误将冒泡至 window,这意味着任何 window.onerror 或者 window.addEventListener('error', callback) 事件都将中断被 componentDidCatch 所捕获到的错误。而在生产环境下则相反,错误并不会冒泡,这意味着任何父级的错误处理器都只会接收到被 componentDidCatch 捕获的非显式错误。

注意

在函数式组件中没有与 componentDidCatch 作用完全相同的函数。如果你想要避免创建类式组件,那么可以单独写一个像上面一样的 错误边界 并在整个应用中使用它。又或者,你可以使用 react-error-boundary 包,它可以完成同样的工作。


componentDidMount()

如果你定义了 componentDidMount 方法,React 将会在组件被添加到屏幕上 (挂载) 后调用它。这里是设置数据获取、订阅监听事件或操作 DOM 节点的常见位置。

如果你要实现 componentDidMount,你通常需要设置一些其他的生命周期函数来避免出错。例如,如果 componentDidMount 读取一些 state 或者 props,你还必须要设置 componentDidUpdate 来处理它们的更新,以及设置 componentWillUnmount 来清理 componentDidMount 的效果。

class ChatRoom extends Component {
state = {
serverUrl: 'https://localhost: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 中设置初始的 state。但是对于 modal 和 tooltip 等当你的渲染依赖于 DOM 节点的大小或位置情况下,这种方法可能是必要的。

注意

对于大多数的使用场景来说,在类式组件中一起定义 componentDidMountcomponentDidUpdatecomponentWillUnmount 等同于在函数式组件中调用 useEffect。在一些少数的情况,例如在浏览器绘制前执行代码很重要时,更像是等同于 useLayoutEffect

了解如何迁移


componentDidUpdate(prevProps, prevState, snapshot?)

如果你定义了 componentDidUpdate 方法,那么 React 会在你的组件更新了 props 或 state 重新渲染后立即调用它。这个方法不会在首次渲染时调用。

你可以在一次更新后使用它来操作 DOM。处理网络请求时也可能会使用这个方法,只要你将当前的 props 与以前的 props 进行比较(因为,如果 props 没有更改,则可能不需要网络请求)。这个方法一般会和 componentDidMount 以及 componentWillUnmount 一起使用:

class ChatRoom extends Component {
state = {
serverUrl: 'https://localhost: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.propsprevProps 以及 this.stateprevState 的条件之中。否则就会存在创建无限循环的风险。

  • 虽然你可以在 componentDidUpdate 中直接调用 setState,但最好尽可能避免这样做。因为它将触发一次发生在浏览器更新屏幕内容之前的额外渲染,在这种情况下,即使 render 会被调用两次,用户也看不到中间状态。这种模式通常会导致性能问题,但是对于 modal 和 tooltip 等当你的渲染依赖于 DOM 节点的大小或位置情况下,这种方法可能是必要的。

注意

对于大多数用例来说,在类式组件中一起定义 componentDidMountcomponentDidUpdatecomponentWillUnmount 等同于在函数式组件中定义 useEffect。在一些少数的情况,例如在浏览器绘制前执行代码很重要时,更像是等同于 useLayoutEffect

了解如何迁移


componentWillMount()

已废弃

此 API 已从 componentWillMount 重命名为 UNSAFE_componentWillMount。旧名称已被弃用,在 React 未来的主版本中,只有新名称才有效。

运行 rename-unsafe-lifecycles codemod 来自动更新你的组件。


componentWillReceiveProps(nextProps)

已废弃

此 API 已从 componentWillReceiveProps 重命名为 UNSAFE_componentWillReceiveProps。旧名称已被弃用,在 React 未来的主版本中,只有新名称才有效。

运行 rename-unsafe-lifecycles codemod 来自动更新你的组件。


componentWillUpdate(nextProps, nextState)

已废弃

此 API 已从 componentWillUpdate 重命名为 UNSAFE_componentWillUpdate。旧名称已被弃用,在 React 未来的主版本中,只有新名称才有效。

运行 rename-unsafe-lifecycles 重构器 来自动更新你的组件。


componentWillUnmount()

如果你定义了 componentWillUnmount 方法,React 会在你的组件被移除屏幕(卸载)之前调用它。此方法常常用于取消数据获取或移除监听事件。

componentWillUnmount 内部的逻辑应该完全“对应”到 componentDidMount 内部的逻辑,例如,如果你在 componentDidMount 中设置了一个监听事件,那么 componentWillUnmount 中就应该清除掉这个监听事件。如果你的 componentWillUnmount 的清理逻辑中读取了一些 props 或者 state,那么你通常还需要实现一个 componentDidUpdate 来清理使用了旧 props 和 state 的资源(例如监听事件)。

class ChatRoom extends Component {
state = {
serverUrl: 'https://localhost: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 不应该返回任何值。

注意

  • 严格模式 开启时,在开发中 React 会在调用 componentDidMount 后立即调用 componentWillUnmount,接着再次调用 componentDidMount。这可以帮助你注意到你是否忘记实现 componentWillUnmount,或者它的逻辑是否没有完全对应到 componentDidMount 的效果。

注意

对于许多用例来说,在类式组件中一起定义 componentDidMountcomponentDidUpdatecomponentWillUnmount 等同于在函数式组件中使用 useEffect。在一些少数的情况,例如在浏览器绘制前执行代码很重要时,更像是等同于 useLayoutEffect

了解如何迁移


forceUpdate(callback?)

强制组件重新渲染。

通常来说,这是没必要的。如果组件的 render 方法仅读取了 this.propsthis.statethis.context 时,当你在组件或其任一父组件内调用 setState 时,它就将自动重新渲染。但是如果组件的 render 方法是直接读取外部数据源时,则必须告诉 React 在该数据源更改时更新用户界面。这就是 forceUpdate 的作用。

尽量避免使用 forceUpdate 并且在 render 中尽量只读取 this.propsthis.state

参数

  • 可选的 如果指定了 callback,React 将在提交完更新后调用你提供的 callback

返回值

forceUpdate 不返回任何值。

注意

注意

读取外部数据源并强制类式组件使用 forceUpdate 来重新渲染更改已被函数式组件中的 useSyncExternalStore 所取代。


getSnapshotBeforeUpdate(prevProps, prevState)

如果你实现了 getSnapshotBeforeUpdate,React 会在 React 更新 DOM 之前时直接调用它。它使你的组件能够在 DOM 发生更改之前捕获一些信息(例如滚动的位置)。此生命周期方法返回的任何值都将作为参数传递给 componentDidUpdate

例如,你可以在像是需要在更新期间保留其滚动位置的聊天消息的 UI 中来使用它。

class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}

getSnapshotBeforeUpdate(prevProps, prevState) {
// 我们是否要向列表中添加新内容?
// 捕获滚动的​​位置,以便我们稍后可以调整滚动。
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}

componentDidUpdate(prevProps, prevState, snapshot) {
// 如果我们有快照值,那么说明我们刚刚添加了新内容。
// 调整滚动,使得这些新内容不会将旧内容推出视野。
//(这里的 snapshot 是 getSnapshotBeforeUpdate 返回的值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}

render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}

在上面的示例中,能直接在 getSnapshotBeforeUpdate 中读取到 scrollHeight 属性非常重要。在 renderUNSAFE_componentWillReceivePropsUNSAFE_componentWillUpdate 中读取它是不安全的,因为这些方法的调用与 React 更新 DOM 之间存在潜在的时间间隔。

参数

  • prevProps:更新之前的 Props。prevProps 将会与 this.props 进行比较来确定发生了什么改变。

  • prevState:更新之前的 State。prevState 将会与 this.state 进行比较来确定发生了什么改变。

返回值

你应该返回你想要的任何类型的快照值,或者是 null。你返回的值将作为第三个参数传递给 componentDidUpdate

注意

  • 如果你定义了 shouldComponentUpdate 并返回了 false,则 getSnapshotBeforeUpdate 不会被调用。

注意

目前,函数式组件中还没有与 getSnapshotBeforeUpdate 等同的方法。这种使用场景非常罕见,但如果你有这种需求,那么你就必须编写一个类式组件。


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.propsthis.statethis.context

你应该将 render 方法编写为纯函数,这意味着如果它使用的 props、state 和 context 相同的话,它应该返回相同的结果。它也不应该包含副作用(例如订阅监听事件)或与浏览器 API 交互。副作用应该发生在事件处理程序或 componentDidMount 等方法中。

参数

render 不需要任何参数。

返回值

render 可以返回任何有效的 React 节点。其中包括 React 元素,例如 <div />、字符串、数字、portals、空节点(nullundefinedtruefalse)以及 React 节点数组。

注意

  • render 应该写成关于 props、state 和 context 的纯函数,它不应该包含副作用。

  • 如果定义了 shouldComponentUpdate 并返回 false 的话,则 render 不会被调用。

  • 严格模式 开启时,React 将在开发过程中调用两次 render,然后丢弃其中一个结果。这可以帮助你注意到需要从 render 方法中移出的意外的副作用。

  • render 的调用和后续的 componentDidMountcomponentDidUpdate 的调用之间没有一一对应的关系。React 可能会在有益的情况下丢弃一些 render 的调用结果。


setState(nextState, callback?)

调用 setState 来更新 React 组件的 state。

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 将组件的 state 的更改加入队列。它告诉 React 该组件及其子组件需要使用新的 state 来重新渲染。这是更新用户界面来响应交互的主要方式。

陷阱

调用 setState不会 更改已执行代码中当前的 state:

function handleClick() {
console.log(this.state.name); // "Taylor"
this.setState({
name: 'Robin'
});
console.log(this.state.name); // 依然是 "Taylor"!
}

它只影响从 下一个 渲染开始返回的 this.state

你还可以将函数传递给 setState。它允许你根据先前的 state 来更新 state:

handleIncreaseAge = () => {
this.setState(prevState => {
return {
age: prevState.age + 1
};
});
}

你不必这样做,但如果你想在同一事件期间多次更新状态,这样就会很方便。

参数

  • nextState:一个对象或者函数。

    • 如果你传递一个对象作为 nextState,它将浅层合并到 this.state 中。
    • 如果你传递一个函数作为 nextState,它将被视为 更新函数。它必须是个纯函数,应该以已加载的 state 和 props 作为参数,并且应该返回要浅层合并到 this.state 中的对象。React 会将你的更新函数放入队列中并重新渲染你的组件。在下一次渲染期间,React 将通过应用队列中的所有的更新函数来计算下一个 state。
  • 可选的 callback:如果你指定该函数,React 将在提交更新后调用你提供的 callback

返回值

setState 不会返回任何值。

注意

  • setState 视为 请求 而会不是立即更新组件的命令。当多个组件更新它们的 state 来响应事件时,React 将批量更新它们,并在这次事件结束时将它们一并重新渲染。在极少数情况下,你需要强制同步应用特定的 state 更新,这时你可以将其包装在 flushSync 中,但这可能会损害性能。

  • setState 不会立即更新 this.state。这让在调用 setState 之后立即读取 setState 成为了一个潜在的陷阱。相反请使用 componentDidUpdate 或设置 setState的 callback 参数,其中任何一个都保证读取 state 将在 state 的更新后触发。如果需要根据前一个 state 来设置 state,那么可以传递给 nextState 一个函数,如上所述。

注意

在类式组件中调用 setState 等同于在函数式组件中调用 set 函数

了解如何迁移


shouldComponentUpdate(nextProps, nextState, nextContext)

如果你定义了 shouldComponentUpdate,React 将调用它来确定是否可以跳过重新渲染。

如果你确定你想手动编写它,你可以将 this.propsnextProps 以及 this.statenextState 进行比较,并返回 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
) {
// 没有任何改变,因此不需要重新渲染
return false;
}
return true;
}

// ...
}

当收到新的 props 或 state 时,React 会在渲染之前调用 shouldComponentUpdate,默认值为 true。初始渲染或使用 forceUpdate 时将不会调用此方法。

参数

  • nextProps:组件即将用来渲染的下一个 props。将 nextPropsthis.props 进行比较以确定发生了什么变化。
  • nextState:组件即将渲染的下一个 state。将 nextStatethis.state 进行比较以确定发生了什么变化。
  • nextContext:组件将要渲染的下一个 context。将 nextContextthis.context 进行比较以确定发生了什么变化。仅当你指定了 static contextType 时才可用。

返回值

如果你希望组件重新渲染的话就返回 true。这是也是默认执行的操作。

返回 false 来告诉 React 可以跳过重新渲染。

注意

  • 此方法 仅仅 作为性能优化而存在。如果你的组件在没有它的情况下损坏,请先修复组件。

  • 可以考虑使用 PureComponent 而不是手写 shouldComponentUpdatePureComponent 会浅比较 props 和 state 以及减少错过必要更新的概率。

  • 我们不建议在 shouldComponentUpdate 中使用深度相等检查或使用 JSON.stringify。因为它使性能变得不可预测并且它与每个 prop 和 state 的数据结构有关。在最好的情况下,你可能会面临给应用程序引入多秒停顿的风险,而在最坏的情况下,你可能会面临使应用程序崩溃的风险。

  • 返回 false 并不会阻止子组件在 他们的 state 发生变化时重新渲染。

  • 返回 false 并不能 确保 组件不会重新渲染。React 将使用返回值作为提示,但如果是出于其他有意义的原因,它仍然可能选择重新渲染你的组件。

注意

使用 shouldComponentUpdate 来优化类式组件与使用 memo 来优化函数式组件类似。函数式组件还使用 useMemo 来提供更精细的优化。


UNSAFE_componentWillMount()

如果你定义了 UNSAFE_componentWillMount,React 会在 constructor 之后就立即调用它。它仅因历史原因而存在,不应在任何新代码中使用。相反请使用其他替代方案:

  • 要初始化状态,请将 state 声明为类字段或在 constructor 内设置 this.state
  • 如果你需要运行额外作用或订阅监听事件事件,请将该逻辑移至 componentDidMount

查看避免不安全生命周期的示例

参数

UNSAFE_componentWillMount 不需要任何参数。

返回值

UNSAFE_componentWillMount 不应该返回任何值。

注意

  • 如果组件实现了 static getDerivedStateFromPropsgetSnapshotBeforeUpdate,则不会调用 UNSAFE_componentWillMount

  • 尽管它的名字是这样的,但是如果你的应用程序使用 Suspense 等新式的 React 功能时,UNSAFE_componentWillMount 不保证组件 被挂载。如果渲染尝试被中止(例如,因为某些子组件的代码尚未加载),那么 React 将丢弃正在进行的树,并在下一次尝试期间尝试从头开始构建组件。这就是为什么这种方法是 不安全 的。依赖于挂载(例如添加监听事件)的代码应放入 componentDidMount

  • UNSAFE_componentWillMount 是运行 服务器渲染 期间运行的唯一生命周期方法。对于所有实际上的用途来说,它与 constructor 相同,因此你应该使用 constructor 来代替这种类型的逻辑。

注意

在类式组件中的 UNSAFE_componentWillMount 内部调用 setState 来初始化状态等同于函数式组件中用该 state 作为初始 state 传递给 useState


UNSAFE_componentWillReceiveProps(nextProps, nextContext)

如果你定义了 UNSAFE_componentWillReceiveProps,React 会在组件收到新的 props 时调用它。它仅因历史原因而存在,不应在任何新代码中使用。相反请使用以下替代方案:

  • 如果你需要 运行副作用(例如,获取数据、运行动画或重新初始化监听)来响应 prop 的更改,那么请将该逻辑移至 componentDidUpdate
  • 如果你需要 避免仅 prop 更改时就重新计算某些数据 时,请使用 memoization helper 来代替。
  • 如果你需要 在 prop 更改时“重置”某些状态 时,请考虑使组件 完全控制 或者 使用 key 使组件完全不受控 来代替。
  • 如果你需要 在 prop 更改时“调整”某些状态 时,请检查你是否可以在渲染期间单独从 props 计算所有必要的信息。如果不能,请使用 static getDerivedStateFromProps 代替。

查看避免不安全生命周期的示例

参数

  • nextProps:组件即将从父组件接收的下一个 props。可以将 nextPropsthis.props 进行比较以确定具体是什么地方发生了变化。
  • nextContext:组件即将从最近的 provider 中接收的下一个 context。可以将 nextContextthis.context 进行比较以确定具体是什么地方发生了变化。仅在指定 static contextType 时可用。

返回值

UNSAFE_componentWillReceiveProps 不应该返回任何值。

注意

  • 如果组件实现了 static getDerivedStateFromPropsgetSnapshotBeforeUpdate,则不会调用 UNSAFE_componentWillReceiveProps

  • 尽管它的名字是这样的,但如果你的应用程序使用 Suspense 等新式的 React 功能时,UNSAFE_componentWillReceiveProps 不保证组件 将会 接收这些 Props。如果渲染尝试被中止(例如,因为某些子组件的代码尚未加载),React 将丢弃正在进行的树,并在下一次尝试期间尝试从头开始构建组件。到下一次渲染尝试时,Props 可能会有所不同。这就是为什么这种方法 不安全。仅为了提交更新(例如重置监听事件)的代码应放入 componentDidUpdate

  • UNSAFE_componentWillReceiveProps 并不意味着组件收到了与上次 不同的 props。你需要自己比较 nextPropsthis.props 以检查是否发生了变化。

  • React 在挂载期间不会使用初始 props 调用 UNSAFE_componentWillReceiveProps。仅当组件的某些属性要更新时,它才会调用此方法。例如,在同一组件内调用 setState 通常不会触发 UNSAFE_componentWillReceiveProps

注意

在类式组件中的 UNSAFE_componentWillReceiveProps 里调用 setState 来“调整”state 等同于在函数式组件在 渲染期间调用来自 useStateset 函数


UNSAFE_componentWillUpdate(nextProps, nextState)

如果你定义了 UNSAFE_componentWillUpdate,React 会在使用新的 props 或 state 渲染之前调用它。它仅因历史原因而存在,不应在任何新代码中使用。相反请使用下面的替代方案:

  • 如果你需要运行副作用(例如,获取数据、运行动画或重新初始化监听)来响应 prop 或 state 的更改,请将该逻辑移至 componentDidUpdate
  • 如果需要从 DOM 中读取一些信息(例如,保存当前滚动位置)以便稍后在 componentDidUpdate 中使用的话,那么请在 getSnapshotBeforeUpdate 中读取这些信息。

查看避免不安全生命周期的示例

参数

  • nextProps:组件即将用来渲染的下一个 props。将 nextPropsthis.props 进行比较以确定发生了什么变化。
  • nextState:组件即将渲染的下一个 state。将 nextStatethis.state 进行比较以确定发生了什么变化。

返回值

UNSAFE_componentWillUpdate 不应该返回任何值。

注意

  • 如果定义了 shouldComponentUpdate 并返回 false,则 UNSAFE_componentWillUpdate 将不会被调用。

  • 如果组件实现了 static getDerivedStateFromPropsgetSnapshotBeforeUpdate,则不会调用 UNSAFE_componentWillUpdate

  • 不支持在 componentWillUpdate 期间调用 setState(或任何导致调用 setState 的方法,例如调度 Redux 操作)。

  • 尽管它的命名是这样,但如果你的应用程序使用如 Suspense 时等新式的 React 功能时,UNSAFE_componentWillUpdate 并不能保证组件 将会 更新。如果渲染尝试被中止(例如,因为某些子组件的代码尚未加载),React 将丢弃正在进行的树,并在下一次尝试期间尝试从头开始构建组件。到下一次渲染尝试时,props 和 state 可能会有所不同。这就是为什么这种方法“不安全”。仅针对提交更新(例如重置监听)而运行的代码应放入 componentDidUpdate

  • UNSAFE_componentWillUpdate 并不意味着组件收到了与上次不同的 props 或 state。你需要自己将 nextPropsthis.props 以及 nextStatethis.state 进行比较,以检查是否发生了变化。

  • React 在挂载期间不会使用初始的 props 和 state 调用 UNSAFE_componentWillUpdate

注意

函数式组件中没有与 UNSAFE_componentWillUpdate 直接等同的方法。


static contextType

如果你想从类式组件中读取 this.context,则必须指定它需要读取哪个 context。你指定为 static contextType 的 context 必须是之前由 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>
);
}
}

注意

在类式组件中读取 this.context 等同于在函数式组件中使用 useContext

了解如何迁移


static defaultProps

你可以定义 static defaultProps 来设置类的默认 props。它们将在 props 为 undefined 或者缺少时有效,但在 props 为 null 时无效。

例如,以下是如何定义 color 属性默认为 blue

class Button extends Component {
static defaultProps = {
color: 'blue'
};

render() {
return <button className={this.props.color}>click me</button>;
}
}

如果 color props 未提供或者为 undefined 时,它将默认设置为 blue

<>
{/* this.props.color 为 “blue” */}
<Button />

{/* this.props.color 为 “blue” */}
<Button color={undefined} />

{/* this.props.color 为 null */}
<Button color={null} />

{/* this.props.color 为 “red” */}
<Button color="red" />
</>

注意

在类式组件中定义 defaultProps 类似于在函数式组件中使用 默认值


static getDerivedStateFromError(error)

如果你定义了 static getDerivedStateFromError,那么当子组件(包括远亲组件)在渲染过程中抛出错误时,React 就会调用它。这使你可以显示错误消息而不是直接清理 UI。

通常,它与 componentDidCatch 一起使用,它可以让你将错误报告发送到某些分析服务。具有这些方法的组件称为 错误边界

查看示例

参数

  • error:被抛出的错误。实际上,它通常是 Error 的实例,但这并不能保证,因为 JavaScript 允许 抛出 任何类型的值,包括字符串甚至是 null

返回值

static getDerivedStateFromError 应该返回告诉组件显示错误消息的 state。

注意

  • static getDerivedStateFromError 应该是一个纯函数。如果你想执行副作用(例如调用分析服务),你还需要实现 componentDidCatch

注意

函数式组件中目前还没有与 static getDerivedStateFromError 直接等同的东西。如果你想避免创建类式组件,请像上面那样编写一个 ErrorBoundary 组件,并在整个应用程序中使用它。或者使用 react-error-boundary 包来执行此操作。


static getDerivedStateFromProps(props, state)

如果你定义了 static getDerivedStateFromProps,React 会在初始挂载和后续更新时调用 render 之前调用它。它应该返回一个对象来更新 state,或者返回 null 就不更新任何内容。

此方法适用于 少数罕见用例,其中 state 取决于 props 随着时间的推移的变化。例如,当 userID 属性更改时,此 Form 组件会重置 email 状态:

class Form extends Component {
state = {
email: this.props.defaultEmail,
prevUserID: this.props.userID
};

static getDerivedStateFromProps(props, state) {
// 每当当前用户发生变化时,
// 重置与该用户关联的任何 state 部分。
// 在这个简单的示例中,只是以 email 为例。
if (props.userID !== state.prevUserID) {
return {
prevUserID: props.userID,
email: props.defaultEmail
};
}
return null;
}

// ...
}

请注意,此模式要求你将 prop 的先前值(如 userID)保留在 state(如 prevUserID)中。

陷阱

派生 state 会导致代码冗长,并使你的组件难以理解。确保你熟悉这些更简单的替代方案

参数

  • props:组件即将用来渲染的下一个 props。
  • state:组件即将渲染的下一个 state。

返回值

static getDerivedStateFromProps 返回一个对象来更新 state,或返回 null 不更新任何内容。

注意

  • 无论什么原因,此方法都会在 每次 渲染时触发。这与 UNSAFE_componentWillReceiveProps 不同,后者仅在父组件不是因为调用了本地的 setState 而重新渲染时触发。

  • 此方法无权访问组件实例。如果你愿意,你可以在 static getDerivedStateFromProps 和其他类方法之间重用一些代码,也就是提取类定义之外的组件 props 和 state 的纯函数。

注意

在类式组件中实现 static getDerivedStateFromProps 等同于在函数式组件中 在渲染期间从 useState 调用 set 函数


用法

定义类式组件

要将 React 组件定义为类,请继承内置的 Component 类并定义 render 方法

import { Component } from 'react';

class Greeting extends Component {
render() {
return <h1>你好, {this.props.name}!</h1>;
}
}

每当 React 需要确定屏幕上显示的内容时,它就会调用你的 render 方法。一般来说,你会让它返回一些 JSX。你的 render 方法应该是一个 纯函数:它应该只计算 JSX。

函数式组件 类似,类式组件可以从它的父组件 通过 props 接收信息。然而,读取 props 的语法是不同的。例如,如果父组件渲染了 <Greeting name="Taylor" /> 组件,那么你可以从 this.props 读取 name 属性,例如 this.props.name

import { Component } from 'react';

class Greeting extends Component {
  render() {
    return <h1>你好, {this.props.name}!</h1>;
  }
}

export default function App() {
  return (
    <>
      <Greeting name="Sara" />
      <Greeting name="Cahal" />
      <Greeting name="Edite" />
    </>
  );
}

请注意,类式组件内部不支持 Hook 函数(以 use 开头的函数,例如 useState)。

陷阱

我们建议使用函数式组件,而不是类式组件。了解如何迁移


向类式组件添加 state

为了向类式组件添加 state,请将一个对象分配给一个名为 state 的属性。要更新 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 或 state 改变而重新渲染后,React 将调用 componentDidUpdate。当你的组件从屏幕上被移除(卸载)后,React 将调用 componentWillUnmount 方法。

如果你实现了 componentDidMount 方法,那么通常还需要实现所有三个生命周期以避免错误。例如,如果 componentDidMount 读取了某些 state 或属性,你就还必须实现 componentDidUpdate 来处理它们的更改,并且实现 componentWillUnmount 来清理 componentDidMount 所执行的所有操作。

例如,这个 ChatRoom 组件使聊天连接与 props 和 state 保持同步:

import { Component } from 'react';
import { createConnection } from './chat.js';

export default class ChatRoom extends Component {
  state = {
    serverUrl: 'https://localhost: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>欢迎来到 {this.props.roomId} 聊天室!</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) {
// 更新状态,以便下一次渲染将显示后备 UI。
return { hasError: true };
}

componentDidCatch(error, info) {
// 示例“组件堆栈”:
// 在 ComponentThatThrows 中(由 App 创建)
// 在 ErrorBoundary 中(由 APP 创建)
// 在 div 中(由 APP 创建)
// 在 App 中
logErrorToMyService(error, info.componentStack);
}

render() {
if (this.state.hasError) {
// 你可以渲染任何自定义后备 UI
return this.props.fallback;
}

return this.props.children;
}
}

然后你可以用它包装组件树的一部分:

<ErrorBoundary fallback={<p>Something went wrong</p>}>
<Profile />
</ErrorBoundary>

如果 Profile 或其子组件抛出错误,ErrorBoundary 将“捕获”该错误,然后显示带有你提供的错误消息的后备 UI,并向你的错误报告服务发送生产错误报告。

你不需要将每个组件包装到单独的错误边界中。当你考虑 错误边界的布置 时,请考虑在哪里显示错误消息才有意义。例如,在消息传递应用程序中,在对话列表周围放置错误边界是有意义的。在每条单独的消息周围放置一个也是有意义的。然而,在每个头像周围设置边界是没有意义的。

注意

目前还没有办法将错误边界编写为函数式组件。但是你不必自己编写错误边界类。例如,你可以使用 react-error-boundary 包来代替。


备选方案

将简单的类式组件迁移为函数式

一般来说,你应该把 组件定义为函数

例如,假设你要将 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() {
// ... 把 render 方法中的代码移动到这里 ...
}

定义 name 属性而不是 this.props.name使用解构语法 来直接读取它:

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" />
    </>
  );
}


将具有 state 的类式组件迁移到函数

假设你要将这个 Counter 从类式组件转换为函数:

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>
      </>
    );
  }
}

首先用必要的 state 变量 来创建一个函数。

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>
    </>
  )
}


将具有生命周期方法的组件从类迁移到函数

假设你要将具有生命周期方法的 ChatRoom 类式组件转换为函数:

import { Component } from 'react';
import { createConnection } from './chat.js';

export default class ChatRoom extends Component {
  state = {
    serverUrl: 'https://localhost: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>欢迎俩到 {this.props.roomId} 聊天室!</h1>
      </>
    );
  }
}

首先,验证你的 componentWillUnmount 是否与 componentDidMount 执行相反的操作。在上面的示例中操作是正确的:它会断开 componentDidMount 设置的连接。如果缺少这样的逻辑,请先添加它。

接下来,验证你的 componentDidUpdate 方法是否可以处理对 componentDidMount 中使用的任何 props 和 state 的更改。在上面的例子中,componentDidMount 调用 setupConnection 来读取 this.state.serverUrlthis.props.roomId。这就是为什么 componentDidUpdate 检查 this.state.serverUrlthis.props.roomId 是否已更改,如果更改了则重置连接。如果你的 componentDidUpdate 逻辑丢失或无法处理所有相关 props 和 state 的更改,那么请首先修复该问题。

在上面的示例中,生命周期方法内的逻辑将组件连接到 React 外部的系统(聊天服务器)。要将组件连接到外部系统,请将此逻辑描述为单个 Effect

import { useState, useEffect } from 'react';

function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost: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://localhost: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>
    </>
  );
}

注意

如果你的组件不与任何外部系统同步,你可能不需要 Effect


将具有 context 的组件从类迁移到函数

在这个例子中,PanelButton 类式组件从 this.context 读取 context

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>注册</Button>
      <Button>登录</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>注册</Button>
      <Button>登录</Button>
    </Panel>
  );
}

export default function MyApp() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  )
}