陷阱

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

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 (现代) 或 static contextTypes (已弃用) 指定要接收的上下文时,它才可用。

一个类组件一次只能读取一个上下文。

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

传递给类组件的 props 可用作 this.props

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

<Greeting name="Taylor" />

注意

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

查看如何迁移。


refs

已弃用

此 API 将在未来 React 的主要版本中移除。 请改用 createRef

允许您访问此组件的旧版字符串 refs


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:组件的初始 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 字段包含一个堆栈跟踪,其中包含抛出错误的组件,以及其所有父组件的名称和源位置。在生产环境中,组件名称将被压缩。如果你设置了生产环境错误报告,则可以使用源映射解码组件堆栈,就像处理常规 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 方法,React 将在您的组件使用更新后的属性或状态重新渲染后立即调用它。此方法不会在初始渲染时调用。

您可以使用它在更新后操作 DOM。这也是发出网络请求的常见位置,只要您将当前属性与之前的属性进行比较(例如,如果属性没有更改,则可能不需要发出网络请求)。通常,您会将其与 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:更新前的属性。将 prevPropsthis.props 进行比较,以确定哪些内容发生了更改。

  • prevState:更新前的状态。将 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 不应该返回任何内容。

注意事项

  • 严格模式 开启时,在开发环境中,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 取代。


getChildContext()

已弃用

此 API 将在未来的 React 主要版本中移除。请改用 Context.Provider

允许您为该组件提供的 旧版上下文 指定值。


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) {
// 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:更新前的属性。将 prevPropsthis.props 进行比较,以确定哪些内容发生了更改。

  • prevState:更新前的状态。将 prevStatethis.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 />、字符串、数字、门户、空节点(nullundefinedtruefalse)以及 React 节点数组。

注意事项

  • render 应该被编写为 props、state 和 context 的纯函数。它不应该有副作用。

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

  • 严格模式 开启时,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,它将被视为一个*更新函数*。它必须是纯函数,应该将待定状态和属性作为参数,并应该返回要浅合并到 this.state 中的对象。React 会将你的更新函数放入队列中,并重新渲染你的组件。在下一次渲染期间,React 将通过将所有排队的更新程序应用于先前的状态来计算下一个状态。
  • **可选** callback:如果指定,React 会在提交更新后调用你提供的 callback

返回值

setState 不返回任何值。

注意事项

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

  • setState 不会立即更新 this.state。这使得在调用 setState 后立即读取 this.state 成为一个潜在的陷阱。请改用 componentDidUpdate 或 setState 的 callback 参数,这两个参数都保证在应用更新后才会触发。如果你需要根据先前的状态设置状态,你可以将一个函数传递给 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(现代)或 static contextTypes(旧版)时才可用。

返回值

如果要重新渲染组件,请返回 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 声明为类字段,或在 constructor 中设置 this.state
  • 如果需要运行副作用或设置订阅,请将该逻辑移至 componentDidMount

请参阅从不安全的生命周期迁移的示例。

参数

UNSAFE_componentWillMount 不接受任何参数。

返回值

UNSAFE_componentWillMount 不应返回任何内容。

注意事项

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

  • 尽管它的名字叫 UNSAFE_componentWillMount,但如果您的应用程序使用像 Suspense 这样的现代 React 特性,它并不能保证组件**会**被挂载。如果渲染尝试被暂停(例如,因为某些子组件的代码尚未加载),React 将丢弃正在进行中的树,并在下次尝试期间尝试从头开始构建组件。这就是为什么这个方法是“不安全的”。依赖于挂载的代码(例如添加订阅)应该放在 componentDidMount 中。

  • UNSAFE_componentWillMount 是在 服务器渲染 期间运行的唯一生命周期方法。实际上,它与 constructor 相同,因此您应该使用 constructor 来处理这类逻辑。

注意

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


UNSAFE_componentWillReceiveProps(nextProps, nextContext)

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

请参阅从不安全的生命周期迁移的示例。

参数

  • nextProps:组件即将从其父组件接收的下一个 props。将 nextPropsthis.props 进行比较,以确定发生了哪些变化。
  • nextContext:组件即将从最近的提供者接收的下一个上下文。将 nextContextthis.context 进行比较,以确定发生了哪些变化。仅当您指定 static contextType(现代)或 static contextTypes(旧版)时才可用。

返回值

UNSAFE_componentWillReceiveProps 不应返回任何内容。

注意事项

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

  • 尽管它的名字叫 UNSAFE_componentWillReceiveProps,但如果您的应用程序使用了像 Suspense 这样的现代 React 功能,它并不能保证组件 *将会* 收到这些 props。如果渲染尝试被暂停(例如,因为某些子组件的代码尚未加载),React 将丢弃正在进行的树,并在下次尝试时从头开始构建组件。在下一次渲染尝试时,props 可能会有所不同。这就是为什么此方法是“不安全”的。只有在提交更新时才应运行的代码(例如重置订阅)应该放在 componentDidUpdate 中。

  • UNSAFE_componentWillReceiveProps 并不意味着组件收到的 props 与上次 *不同*。您需要自己比较 nextPropsthis.props,以检查是否有任何变化。

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

注意

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


UNSAFE_componentWillUpdate(nextProps, nextState)

如果您定义了 UNSAFE_componentWillUpdate,React 将在使用新的 props 或 state 进行渲染之前调用它。它只是出于历史原因而存在,不应在任何新代码中使用。请改用以下替代方法之一

  • 如果您需要根据 props 或 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 操作)。

  • 尽管它的名字叫 UNSAFE_componentWillUpdate,但如果您的应用程序使用现代 React 功能(如 Suspense),它并不能保证组件*会*更新。 如果渲染尝试被暂停(例如,因为某些子组件的代码尚未加载),React 将丢弃正在进行的树,并在下次尝试期间尝试从头开始构建组件。 到下次渲染尝试时,props 和 state 可能会有所不同。 这就是为什么此方法是“不安全的”。 应该仅针对已提交的更新运行的代码(例如重置订阅)应该进入 componentDidUpdate

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

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

注意

在函数组件中没有直接等效于 UNSAFE_componentWillUpdate 的方法。


static childContextTypes

已弃用

此 API 将在未来 React 的主要版本中删除。 请改用 static contextType

允许您指定此组件提供哪些 旧版上下文


static contextTypes

已弃用

此 API 将在未来 React 的主要版本中删除。 请改用 static contextType

允许您指定此组件使用哪些 旧版上下文


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 propTypes

您可以将 static propTypesprop-types 库一起使用,以声明您的组件接受的 props 类型。这些类型将在渲染期间进行检查,并且仅在开发环境中进行。

import PropTypes from 'prop-types';

class Greeting extends React.Component {
static propTypes = {
name: PropTypes.string
};

render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}

注意

我们建议使用 TypeScript 而不是在运行时检查 prop 类型。


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 的变化。例如,当 userID prop 更改时,此 Form 组件会重置 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;
}

// ...
}

请注意,此模式要求您在状态(例如 prevUserID)中保留 prop(例如 userID)的上一个值。

陷阱

派生状态会导致代码冗长,并使您的组件难以理解。确保您熟悉更简单的替代方法:

参数

  • props:组件即将使用的新 props。
  • 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 会在您的组件被添加到屏幕上(挂载)时调用它。当您的组件由于属性或状态的更改而重新渲染后,React 会调用 componentDidUpdate。在您的组件从屏幕上移除(卸载)后,React 会调用 componentWillUnmount

如果您实现了 componentDidMount,通常需要实现所有三个生命周期方法以避免错误。例如,如果 componentDidMount 读取了一些状态或属性,您还需要实现 componentDidUpdate 来处理它们的更改,以及实现 componentWillUnmount 来清理 componentDidMount 所做的任何事情。

例如,这个 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>
      </>
    );
  }
}

请注意,在开发过程中,当 严格模式 启用时,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 ...
}

使用 解构语法 定义 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" />
    </>
  );
}


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

假设您要将这个 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>
  )
}