陷阱

我们建议将组件定义为函数而不是类。 查看如何迁移。

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

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

参考

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

注意

在类组件中读取this.context 等效于函数组件中的useContext

查看如何迁移。


props

传递给类组件的属性可作为this.props使用。

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

<Greeting name="Taylor" />

注意

在类组件中读取this.props 等效于在函数组件中声明属性

查看如何迁移。


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

注意

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

查看如何迁移。


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 方法。但是,生命周期方法(如 componentDidMountcomponentWillUnmount)不会在服务器上运行。

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

注意

函数组件中没有 constructor 的确切等效项。要在函数组件中声明状态,请调用 useState。为了避免重新计算初始状态,将函数传递给 useState


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.onerrorwindow.addEventListener('error', callback)都将拦截componentDidCatch捕获的错误。相反,在生产环境中,错误不会冒泡,这意味着任何祖先错误处理程序只会接收componentDidCatch未显式捕获的错误。

注意

函数组件中尚无componentDidCatch的直接等效项。如果您想避免创建类组件,请编写一个如上所示的单个ErrorBoundary组件并在整个应用程序中使用它。或者,您可以使用react-error-boundary包,它可以为您完成此操作。


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 节点时,这可能是必要的。

注意

对于许多用例,在类组件中一起定义componentDidMountcomponentDidUpdatecomponentWillUnmount等效于在函数组件中调用useEffect。在少数情况下,如果代码必须在浏览器绘制之前运行,useLayoutEffect更匹配。

查看如何迁移。


componentDidUpdate(prevProps, prevState, snapshot?)

如果您定义了componentDidUpdate方法,则在组件使用更新后的 props 或 state 重新渲染后,React 会立即调用它。此方法不会在初始渲染时调用。

您可以使用它在更新后操作 DOM。只要将当前 props 与之前的 props 进行比较(例如,如果 props 没有更改,则可能不需要网络请求),这也是进行网络请求的常见位置。通常,您会将它与componentDidMountcomponentWillUnmount一起使用:

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。比较prevPropsthis.props以确定发生了哪些更改。

  • prevState:更新前的 State。比较prevStatethis.state以确定发生了哪些更改。

  • snapshot:如果您实现了getSnapshotBeforeUpdate,则snapshot将包含您从该方法返回的值。否则,它将为undefined

返回值

componentDidUpdate不应该返回任何内容。

注意事项

  • 如果定义了shouldComponentUpdate并返回false,则不会调用componentDidUpdate

  • componentDidUpdate 内部的逻辑通常应该用条件语句包装起来,这些条件语句比较this.propsprevProps,以及 this.stateprevState。否则,存在创建无限循环的风险。

  • 虽然你可以在 componentDidUpdate 中立即调用 setState,但最好尽量避免这种情况。它会触发额外的渲染,但这会在浏览器更新屏幕之前发生。这保证了即使在这种情况下 render 会被调用两次,用户也不会看到中间状态。这种模式经常导致性能问题,但在某些罕见情况下(例如模态框和工具提示,你需要在渲染依赖于其大小或位置的内容之前测量 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 codemod来自动更新你的组件。


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 的功能。

注意

对于许多用例,在类组件中一起定义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 潜在更改之前捕获 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 属性非常重要。在 renderUNSAFE_componentWillReceivePropsUNSAFE_componentWillUpdate 中读取它是不安全的,因为这些方法被调用与 React 更新 DOM 之间存在潜在的时间差。

参数

  • prevProps:更新前的 Props。比较prevPropsthis.props以确定发生了哪些更改。

  • prevState:更新前的 State。比较prevStatethis.state以确定发生了哪些更改。

返回值

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

注意事项

注意

目前,函数组件没有等同于 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 的纯函数。它不应该有副作用。

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

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

  • render 调用与其后的 componentDidMountcomponentDidUpdate 调用之间并非一一对应关系。当 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 不会更改正在执行的代码中当前的状态。

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

它只会影响从下一个渲染开始 this.state 将返回的内容。

您也可以将函数传递给 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 成为一个潜在的陷阱。相反,请使用 componentDidUpdatesetStatecallback 参数,这两者都保证在应用更新后触发。如果您需要根据先前状态设置状态,则可以像上面描述的那样将函数传递给 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
) {
// Nothing has changed, so a re-render is unnecessary
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不会阻止子组件在*其*状态发生变化时重新渲染。

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

注意

使用shouldComponentUpdate优化类组件类似于使用memo优化函数组件。函数组件还提供更细粒度的优化,可以使用useMemo


UNSAFE_componentWillMount()

如果您定义了UNSAFE_componentWillMount,React 将在constructor之后立即调用它。它只出于历史原因存在,不应在任何新代码中使用。请改用其中一种替代方法。

  • 要初始化 state,请将state声明为类字段,或在constructor内部设置this.state
  • 如果您需要运行副作用或设置订阅,请将该逻辑移至componentDidMount

查看从不安全的生命周期迁移的示例。

参数

UNSAFE_componentWillMount 不接受任何参数。

返回值

UNSAFE_componentWillMount 不应该返回任何值。

注意事项

  • 尽管名称如此,如果组件实现了static getDerivedStateFromPropsgetSnapshotBeforeUpdateUNSAFE_componentWillMount 不会被调用。

  • 尽管其名称如此,UNSAFE_componentWillMount 并不能保证组件一定会被挂载,尤其是在你的应用使用了现代 React 特性,例如 Suspense。如果渲染尝试被挂起(例如,因为某些子组件的代码尚未加载),React 将丢弃正在进行的树,并在下一次尝试期间尝试从头开始构建组件。这就是为什么此方法“不安全”。依赖于挂载的代码(例如添加订阅)应该放在 componentDidMount 中。

  • UNSAFE_componentWillMount 是唯一在 服务器端渲染 期间运行的生命周期方法。实际上,它与 constructor 完全相同,因此你应该改用 constructor 来实现这种类型的逻辑。

注意

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


UNSAFE_componentWillReceiveProps(nextProps, nextContext)

如果你定义了 UNSAFE_componentWillReceiveProps,当组件接收到新的 props 时,React 将调用它。它只出于历史原因而存在,不应该在新代码中使用。请改用以下替代方案。

  • 如果你需要运行副作用(例如,获取数据、运行动画或重新初始化订阅),请将该逻辑移动到 componentDidUpdate
  • 如果你需要仅在 prop 更改时避免重新计算某些数据,请改用 记忆化辅助函数
  • 如果你需要在 prop 更改时“重置”某些状态,请考虑将组件设为 完全受控的完全不受控的(带 key)
  • 如果你需要在 prop 更改时“调整”某些状态,请检查是否可以在渲染期间仅从 props 中计算所有必要信息。如果不能,请改用 static getDerivedStateFromProps

查看从不安全的生命周期迁移的示例。

参数

  • nextProps:组件即将从其父组件接收的下一个 props。比较 nextPropsthis.props 以确定发生了哪些更改。
  • nextContext:组件即将从最近的提供者接收的下一个上下文。比较 nextContextthis.context 以确定发生了哪些更改。仅当您指定 static contextType 时才可用。

返回值

UNSAFE_componentWillReceiveProps 不应返回任何值。

注意事项

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

  • 尽管名称如此,UNSAFE_componentWillReceiveProps 并不能保证组件一定会接收到这些props,尤其当你的应用使用了现代React特性例如Suspense。如果渲染尝试被挂起(例如,因为某个子组件的代码尚未加载),React 将丢弃正在进行的树,并在下一次尝试期间尝试从头开始构建组件。在下一次渲染尝试时,props 可能已经不同了。这就是为什么此方法“不安全”。仅应为已提交的更新运行的代码(例如重置订阅)应放入componentDidUpdate

  • UNSAFE_componentWillReceiveProps 并不能表示组件收到了与上次不同的props。你需要自己比较nextPropsthis.props 来检查是否有任何变化。

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

注意

在类组件的UNSAFE_componentWillReceiveProps 内调用setState 来“调整”状态,等同于在函数组件中在渲染期间调用set 函数(来自useState


UNSAFE_componentWillUpdate(nextProps, nextState)

如果你定义了UNSAFE_componentWillUpdate,React 将在使用新的 props 或 state 渲染之前调用它。它仅出于历史原因存在,不应在任何新代码中使用。请改用其中一种替代方法。

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

查看从不安全的生命周期迁移的示例。

参数

  • nextProps:组件即将使用的下一个 props。比较nextPropsthis.props来确定发生了哪些变化。
  • nextState:组件即将渲染的下一个状态。比较nextStatethis.state 以确定发生了哪些变化。

返回值

UNSAFE_componentWillUpdate 不应返回任何值。

注意事项

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

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

  • componentWillUpdate期间,不支持调用setState(或任何导致调用setState的方法,例如分发Redux action)。

  • 尽管名称如此,如果你的应用使用了现代React特性,例如SuspenseUNSAFE_componentWillUpdate并不能保证组件一定会更新。如果渲染尝试被挂起(例如,因为某些子组件的代码尚未加载),React将丢弃正在进行的树,并在下次尝试时尝试从头开始构建组件。在下一次渲染尝试时,props和state可能已经不同了。这就是为什么此方法“不安全”。应该仅针对已提交的更新运行的代码(例如重置订阅)应该放在componentDidUpdate中。

  • UNSAFE_componentWillUpdate并不意味着组件收到了与上次不同的props或state。你需要自己比较nextPropsthis.props以及nextStatethis.state来检查是否有更改。

  • React在挂载过程中不会使用初始props和state调用UNSAFE_componentWillUpdate

注意

函数组件中没有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>
);
}
}

注意

在类组件中读取this.context 等效于函数组件中的useContext

查看如何迁移。


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" />
</>

注意

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


static getDerivedStateFromError(error)

如果定义了static getDerivedStateFromError,则当子组件(包括远端子组件)在渲染期间抛出错误时,React将调用它。这允许你显示错误消息,而不是清除UI。

通常情况下,它与componentDidCatch一起使用,后者允许你将错误报告发送到某些分析服务。具有这些方法的组件称为错误边界

参见示例。

参数

  • error:抛出的错误。实际上,它通常是Error的一个实例,但这并非绝对,因为JavaScript允许throw任何值,包括字符串甚至null

返回值

static getDerivedStateFromError应该返回指示组件显示错误消息的状态。

注意事项

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

注意

函数组件目前还没有 static getDerivedStateFromError 的直接等效项。如果您想避免创建类组件,请编写一个像上面那样的单个 ErrorBoundary 组件并在整个应用程序中使用它。或者,使用 react-error-boundary 包,它可以做到这一点。


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 和其他类方法之间重用一些代码。

注意

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


使用

定义类组件

要将 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,并将生产错误报告发送到您的错误报告服务。

您不需要将每个组件都包装到单独的错误边界中。当您考虑错误边界的粒度时,请考虑在何处显示错误消息最合适。例如,在消息应用程序中,将错误边界放在对话列表周围是合理的。将其放在每个单独的消息周围也是合理的。但是,将边界放在每个头像周围就没有意义了。

注意

目前无法将错误边界编写为函数组件。但是,您不必自己编写错误边界类。例如,您可以使用 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() {
// ... 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" />
    </>
  );
}


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

假设您正在将这个 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>
      </>
    );
  }
}

首先声明一个具有必要的状态变量的函数:

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

注意

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


将具有上下文的组件从类组件迁移到函数组件

在这个例子中,PanelButton 类组件从上下文 读取this.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>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>
  )
}