Component
Component 是被定义为 JavaScript 类 的 React 基类,类组件仍然被 React 支持,但我们不建议在新代码中使用它们。
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}- 参考
Componentcontextpropsrefsstateconstructor(props)componentDidCatch(error, info)componentDidMount()componentDidUpdate(prevProps, prevState, snapshot?)componentWillMount()componentWillReceiveProps(nextProps)componentWillUpdate(nextProps, nextState)componentWillUnmount()forceUpdate(callback?)getChildContext()getSnapshotBeforeUpdate(prevProps, prevState)render()setState(nextState, callback?)shouldComponentUpdate(nextProps, nextState, nextContext)UNSAFE_componentWillMount()UNSAFE_componentWillReceiveProps(nextProps, nextContext)UNSAFE_componentWillUpdate(nextProps, nextState)static childContextTypesstatic contextTypesstatic contextTypestatic defaultPropsstatic propTypesstatic getDerivedStateFromError(error)static getDerivedStateFromProps(props, state)
- 用法
- 备选方案
参考
Component
要想将React组件定义为一个类,你需要扩展内置的 Component 类并定义一个 render 方法:
import { Component } from 'react';
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}只有 render 方法是必要的,其他方法是可选的。
context
一个类组件的 context 可以通过使用 this.context 来实现。 只有当你使用 static contextType (更新的)或者 static contextTypes (已被废弃) 来特别指定你想要接受 哪一个 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>
);
}
}props
传递给类组件的 props 的有效形式为 this.props。
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
<Greeting name="Taylor" />refs
允许你获取此组件的 legacy string refs
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}>
Increment age
</button>
<p>You are {this.state.age}.</p>
</>
);
}
}constructor(props)
constructor 在你的类组件 挂载(添加到屏幕上)之前运行,一般来说,在 React 中 constructor 仅用于两个目的。它可以让你来声明 state 以及将你的类方法 bind 到你的类实例上。
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 = () => {
// ...
}constructor 不应该包含任何额外作用或者监听相关。
参数
props: 组件初始的 props。
返回值
constructor 不应该返回任何东西。
说明
-
不要在 constructor 中运行任何任额外作用或者监听相关的代码。相反,我们使用
componentDidMount来解决这个问题。 -
在 constructor 中,你需要在其他声明之前调用
super(props)。如果你不这样做,当 constructor 运行时this.props就会为undefined, 这可能会让人迷惑并且导致错误。 -
Constructor 是唯一一个你能直接赋值
this.state的地方。 在其余所有方法中,你需要使用this.setState()来代替。不要使用在 constructor 中使用setState。 -
当你使用 服务端渲染 时, constructor 也将在服务端运行,紧接着是
render方法。 然而,像是componentDidMount或者componentWillUnmount这样的生命周期方法将不会在服务端运行。 -
当 严格模式 打开时, React 将会在开发过程中调用两次
constructor然后丢弃其中的一个实例。这有助于你注意到需要从constructor中移出的意外副作用。
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.onerror或者window.addEventListener('error', callback)都将拦截被componentDidCatch所捕获到的错误。在生产环境下,相反,错误并不会冒泡, 这意味着任何祖先级的错误处理器都只会接收到被componentDidCatch捕获的非显式错误。
componentDidMount()
如果你定义了 componentDidMount 方法, React 将会在你的组件添加上屏幕 (mounted) 时调用它。这是开始数据获取、设置监听或操作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。 但是,对于模态和工具提示等情况,当你需要在呈现依赖于其大小或位置的内容之前测量 DOM 节点时,它可能是必要的。
componentDidUpdate(prevProps, prevState, snapshot?)
如果你定义了 componentDidUpdate 方法,当你的组件更新了 props 或 state 重新渲染后,React 将立即调用它。这个方法不会在首次渲染时调用。
你可以在更新后使用它来操作 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.props与prevProps和this.state与prevState之中。 否则,就会存在创建无限循环的风险。 -
虽然可以在
componentDidUpdate中立即调用setState,但最好尽可能避免这样做。它将触发一个额外的渲染,但它将在浏览器更新屏幕之前发生。 这导致了即使render在这种情况下会被调用两次, 用户也不会看到中间状态。这种模式通常会导致性能问题,但在模态和工具提示等少数情况下,当你需要在呈现依赖于其大小或位置的内容之前测量DOM节点时,可能需要使用这种模式。
componentWillMount()
componentWillReceiveProps(nextProps)
componentWillUpdate(nextProps, nextState)
componentWillUnmount()
如果你定义了 componentWillUnmount 方法,React 会在你的组件被移除屏幕之前 (unmounted) 调用它。这是取消数据获取或删除监听的常见位置。
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的作用。
forceUpdate(callback?)
强制组件重新渲染。
通常来说,这是没有必要的。如果组件的 render 方法仅读取了 this.props 、 this.state 或 this.context, 时,当你在组件或其父组件之一内调用 setState 时,它就将自动重新渲染。但是,如果组件的 render 方法直接从外部数据源读取,则必须告诉 React 在该数据源更改时更新用户界面。这就是你可以用 forceUpdate 做的事。
尽量避免使用 forceUpdate 并且在 render 中只读取 this.props 和 this.state。
参数
- optional
callback如果有指定,React 将在提交更新后调用你提供的callback。
返回值
forceUpdate 不返回任何值。
说明
- 如果你调用了
forceUpdate, React 将重新渲染而且不会调用shouldComponentUpdate。
getChildContext()
允许你指定由该组件提供的 legacy context 的值。
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 属性非常重要。在 render 、 UNSAFE_componentWillReceiveProps 或 UNSAFE_componentWillUpdate 中读取它是不安全的,因为在这些方法被调用和 React 更新 DOM 之间存在潜在的时间间隔。
参数
-
prevProps: 更新之前的 Props。prevProps将会与this.props进行比较来确定是否改变。 -
prevState: 更新之前的 State。prevState将会与this.state进行比较来确定是否改变。
返回值
你应该返回你想要的任何类型的快照值,或者 null。你返回的值将作为第三个参数传递给 componentDidUpdate。。
说明
- 如果定义了
shouldComponentUpdate并返回了false,则不会调用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.props、this.state 和 this.context。
你应该将 render 方法编写为纯函数,这意味着如果 props、state 和 context 相同,它应该返回相同的结果。它也不应该包含额外的作用(例如设置监听)或与浏览器 API 交互。额外的作用应该发生在事件处理程序或 componentDidMount. 等方法中。
参数
-
prevProps: 更新之前的 Props。prevProps将会与this.props进行比较来确定是否改变。 -
prevState: 更新之前的 State。prevState将会与this.state进行比较来确定是否改变。
返回值
render 可以返回任何有效的 React 节点。这包括 React 元素,例如 <div />、字符串、数字、 portals 、空节点(null、undefined、true 和 false )和 React 节点数组。
说明
-
render应该写成 props 、 state 和 context 的纯函数,它不应该包含额外的作用。 -
如果定义了
shouldComponentUpdate并返回false,则不会调用render。 -
当 严格模式 开启时,React 将在开发过程中调用
render两次,然后丢弃其中一个结果。这可以帮助你注意到需要从render方法中移出的意外副作用。 -
render调用和后续的componentDidMount或componentDidUpdate调用之间没有一一对应的关系。当这样更好时,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}.
</>
);
}
}setState 将组件的 state 的更改加入队列。它告诉 React 该组件及其子组件需要使用新状态重新渲染。这是更新用户界面以响应交互的主要方式。
你还可以将函数传递给 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 将在提交更新后调用你提供的回调。
返回值
setState 不会返回任何值。
说明
-
将
setState视为 请求,而不是立即会更新组件的命令。当多个组件更新它们的 state 以响应事件时, React 将批量更新它们,并在这次事件结束时将它们一起重新渲染。在极少数情况下,你需要强制同步应用特定的状态更新,你可以将其包装在flushSync, 中,但这可能会损害性能。 -
setState不会立即更新this.state。这让在调用setState之后立即读取setState成为一个潜在的陷阱。相反,请使用componentDidUpdate或 setStatecallback参数,其中任何一个都保证在更新后触发。如果需要根据前一个状态来设置状态,可以将函数传递给nextState,如上所述。
shouldComponentUpdate(nextProps, nextState, nextContext)
如果你定义了 shouldComponentUpdate,React 将调用它来确定是否可以跳过重新渲染。
如果你确信你想手动编写它,你可以将this.props与nextProps进行比较,将this.state与nextState进行比较,并返回false来告诉React可以跳过更新。
class Rectangle extends Component {
state = {
isHovered: false
};
shouldComponentUpdate(nextProps, nextState) {
if (
nextProps.position.x === this.props.position.x &&
nextProps.position.y === this.props.position.y &&
nextProps.size.width === this.props.size.width &&
nextProps.size.height === this.props.size.height &&
nextState.isHovered === this.state.isHovered
) {
// 没有任何改变,因此不需要重新渲染
return false;
}
return true;
}
// ...
}当收到新的 props 或 state 时,React 会在渲染之前调用 shouldComponentUpdate,默认为 true。初始渲染或使用 forceUpdate 时将不会调用此方法。
参数
nextProps: 组件即将用来渲染的下一个 props。将nextProps与this.props进行比较以确定发生了什么变化。nextState: 组件即将渲染的下一个 state。将nextState与this.state进行比较以确定发生了什么变化。nextContext: 组件将要渲染的下一个上下文。将nextContext与this.context进行比较以确定发生了什么变化。仅当你指定了static contextType(更新的)或static contextTypes(旧版)时才可用。
返回值
返回 true 如果你希望组件重新渲染。这是默认返回。
返回 false 来告诉 React 可以跳过重新渲染。
说明
-
此方法 仅仅 作为性能优化而存在。如果你的组件在没有它的情况下损坏,请先修复组件。
-
可以考虑使用
PureComponent而不是手动编写shouldComponentUpdate。PureComponent浅层比较 props 和 state,并减少你跳过必要更新的机会。 -
我们不建议在
shouldComponentUpdate中进行深度相等检查或使用JSON.stringify。它使性能变得不可预测,并且依赖于每个 prop 和 state 的数据结构。在最好的情况下,你可能会冒着给应用程序引入多秒停顿的风险,而在最坏的情况下,你可能会面临使应用程序崩溃的风险。 -
返回
false并不会阻止子组件在 他们的 state 发生变化时重新渲染。 -
返回
false并不能 确保 组件不会重新渲染。 React 将使用返回值作为提示,但如果出于其他有意义原因,它仍然可能选择重新渲染你的组件。
UNSAFE_componentWillMount()
如果你定义了 UNSAFE_componentWillMount,React 会在 constructor 之后立即调用它。它仅因历史原因而存在,不应在任何新代码中使用。相反,请使用一种替代方案:
-
要初始化状态,请将
state声明为类字段或在constructor. 内设置this.state。 -
如果你需要运行额外作用或设置监听,请将该逻辑移至
componentDidMount。
参数
UNSAFE_componentWillMount 不需要任何参数。
返回值
UNSAFE_componentWillMount 不应该返回任何值。
说明
-
如果组件实现了
static getDerivedStateFromProps或getSnapshotBeforeUpdate.,则不会调用UNSAFE_componentWillMount. -
即使它的名字是这样的,但是如果你的应用程序使用
Suspense. 等现代 React 功能时,UNSAFE_componentWillMount不保证组件 将 被安装。(例如,因为某些子组件的代码尚未加载。) React 将丢弃正在进行的树,并在下一次尝试期间尝试从头开始构建组件。 这就是为什么这种方法是 不安全 的。依赖于挂载(例如添加监听)的代码应放入componentDidMount。 -
UNSAFE_componentWillMount是用于所有实际目的在服务器渲染期间运行的唯一生命周期方法,它与constructor, 相同,因此你应该使用constructor来代替这种类型的逻辑。
UNSAFE_componentWillReceiveProps(nextProps, nextContext)
如果你定义了 UNSAFE_componentWillReceiveProps,React 会在组件收到新的 props 时调用它。它仅因历史原因而存在,不应在任何新代码中使用。相反,请使用以下替代方案:
- 如果你需要 运行额外作用 (例如,获取数据、运行动画或重新初始化监听)来响应 prop 更改,请将该逻辑移至
componentDidUpdate - 如果你需要 避免仅在 prop 更改时重新计算某些数据 请使用 memoization helper 代替。
- 如果你需要 在 prop 更改时
重置某些状态考虑制作一个组件 fully uncontrolled 或者 fully uncontrolled with a key instead. - 如果你需要 在 prop 更改时
调整某些状态, 检查你是否可以在渲染期间单独从 props 计算所有必要的信息。如果不能,请改用static getDerivedStateFromProps。
参数
nextProps: 组件将从其父组件接收的下一个 props。将nextProps与this.props进行比较以确定发生了什么变化。nextContext: 组件将从最近的提供者处接收的下一个 props。将nextContext与this.context进行比较以确定发生了什么变化。仅当你指定static contextType(更新的)或static contextTypes(旧版)时才可用。
返回值
UNSAFE_componentWillReceiveProps 不应该返回任何值。
说明
-
如果组件实现了
static getDerivedStateFromProps或getSnapshotBeforeUpdate.,则不会调用UNSAFE_componentWillReceiveProps -
即使它的名字是这样的, 如果你的应用程序使用
Suspense. 等现代 React 功能,UNSAFE_componentWillReceiveProps不保证组件 将 接收这些 Props (例如,因为某些子组件的代码尚未加载), React 将丢弃正在进行的树,并在下一次尝试期间尝试从头开始构建组件。 到下一次渲染尝试时,Props 可能会有所不同。这就是为什么这种方法 不安全。仅为提交更新(例如重置监听)的代码应放入componentDidUpdate. -
UNSAFE_componentWillReceiveProps并不意味着组件收到了与上次 不同的 props。你需要自己比较nextProps和this.props以检查是否发生了变化。 -
React 在挂载期间不会使用初始 props 调用
UNSAFE_componentWillReceiveProps。仅当组件的某些属性要更新时,它才会调用此方法。例如,在同一组件内调用setState通常不会触发UNSAFE_componentWillReceiveProps。
UNSAFE_componentWillUpdate(nextProps, nextState)
如果你定义了UNSAFE_componentWillUpdate,React 会在使用新的 props 或 state 渲染之前调用它。它仅因历史原因而存在,不应在任何新代码中使用。相反,请使用下面的替代方案:
- 如果你需要运行额外作用(例如,获取数据、运行动画或重新初始化监听)来响应 prop 或 state 更改,请将该逻辑移至
componentDidUpdate。 - 如果需要从 DOM 中读取一些信息(例如,保存当前滚动位置)以便稍后在
componentDidUpdate中使用的话,那么请在getSnapshotBeforeUpdate中读取。
参数
-
nextProps: 组件即将用来渲染的下一个 props。将nextProps与this.props进行比较以确定发生了什么变化。 -
nextState: 组件即将渲染的下一个 state。将nextState与this.state进行比较以确定发生了什么变化。
返回值
UNSAFE_componentWillUpdate 不应该返回任何值。
说明
-
如果定义了
shouldComponentUpdate并返回false,则UNSAFE_componentWillUpdate将不会被调用。 -
如果组件实现了
static getDerivedStateFromProps或getSnapshotBeforeUpdate.,则不会调用UNSAFE_componentWillUpdate -
不支持在
componentWillUpdate期间调用setState(或任何导致调用setState的方法,例如分派 Redux 操作)。 -
尽管它的命名是这样,
UNSAFE_componentWillUpdate并不能保证如果你的应用程序使用现代 React 功能(如Suspense. )时,组件 将会 更新(例如,因为某些子组件的代码尚未加载),React 将丢弃正在进行的树,并在下一次尝试期间尝试从头开始构建组件。到下一次渲染尝试时,props 和 state 可能会有所不同。 这就是为什么这种方法“不安全”。仅针对提交更新(例如重置监听)运行的代码应放入componentDidUpdate。 -
UNSAFE_componentWillUpdate并不意味着组件收到了与上次不同的 props 或状态。你需要自己将nextProps与this.props以及nextState与this.state进行比较,以检查是否发生了变化。 -
React 在挂载期间不会使用初始 props 和 state 调用
UNSAFE_componentWillUpdate。
static childContextTypes
允许你指定此组件提供哪个旧版上下文。
static contextTypes
允许你指定此组件使用哪个旧版上下文。
static contextType
如果你想从类组件中读取 this.context,则必须指定它需要读取哪个 context。你指定为 static contextType 的上下文必须是之前由 createContext 创建的值。
class Button extends Component {
static contextType = ThemeContext;
render() {
const theme = this.context;
const className = 'button-' + theme;
return (
<button className={className}>
{this.props.children}
</button>
);
}
}static defaultProps
你可以定义 static defaultProps 来设置类的默认 props。它们将在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 is "blue" */}
<Button />
{/* this.props.color is "blue" */}
<Button color={undefined} />
{/* this.props.color is null */}
<Button color={null} />
{/* this.props.color is "red" */}
<Button color="red" />
</>static propTypes
你可以定义 static propTypes 和 prop-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>
);
}
}static getDerivedStateFromError(error)
如果你定义了static getDerivedStateFromError,当子组件(包括远程子组件)在渲染过程中抛出错误时,React 将调用它。这使你可以显示错误消息而不是清除 UI。
通常,它与 componentDidCatch 一起使用,它可以让你将错误报告发送到某些分析服务。具有这些方法的组件称为 错误边界。
参数
返回值
static getDerivedStateFromError 应该返回告诉组件显示错误消息的状态。
说明
static getDerivedStateFromError应该是一个纯函数。如果你想执行额外作用(例如,调用分析服务),你还需要实现componentDidCatch.
static getDerivedStateFromProps(props, state)
如果你定义了 static getDerivedStateFromProps,React 会在初始挂载和后续更新时调用 render 之前调用它。它应该返回一个对象来更新状态,或者返回 null 不更新任何内容。
此方法适用于少数罕见用例,其中 state 取决于 props 随着时间的推移的变化。例如,当 userID 属性更改时,此 Form 组件会重置 email 状态:
class Form extends Component {
state = {
email: this.props.defaultEmail,
prevUserID: this.props.userID
};
static getDerivedStateFromProps(props, state) {
// 每当当前用户发生变化时,
// 重置与该用户关联的任何 state 部分。
// 在这个简单的示例中,只是以电子邮件为例。
if (props.userID !== state.prevUserID) {
return {
prevUserID: props.userID,
email: props.defaultEmail
};
}
return null;
}
// ...
}请注意,此模式要求你将 prop 的先前值(如 userID )保留在状态(如 prevUserID )中。
参数
props: 组件即将用来渲染的下一个 props。state: 组件即将渲染的下一个 state。
返回值
static getDerivedStateFromProps 返回一个对象以更新状态,或返回 null 不更新任何内容。
说明
-
无论原因如何,此方法都会在 每个 渲染时触发。这与
UNSAFE_componentWillReceiveProps不同,后者仅在父级导致重新渲染时触发,而不是由于本地setState的结果。 -
此方法无权访问组件实例。如果你愿意,你可以通过在类定义之外提取组件 props 和 state 的纯函数,在
static getDerivedStateFromProps和其他类方法之间重用一些代码。
用法
定义类组件
要将 React 组件定义为类,请扩展内置的 Component 类并定义 render 方法:
import { Component } from 'react';
class Greeting extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}每当 React 需要确定屏幕上显示的内容时,它就会调用你的 render 方法。一般来说,你将让它返回一些 JSX 你的 render 方法应该是一个纯函数:,它应该只计算 JSX。
与函数式组件类似,类组件可以从它的父组件通过props接收信息。然而,读取 props 的语法是不同的。例如,如果父组件渲染 <Greeting name="Taylor" />,那么你可以从 this.props 读取 name 属性,例如 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" /> </> ); }
请注意,类组件内部不支持 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>Welcome to the {this.props.roomId} room!</h1> </> ); } }
请注意,在开发中,当 严格模式 开启时,React 将在调用 componentDidMount 后,立即调用 componentWillUnmount,然后再次调用 componentDidMount。这可以帮助你注意到你是否忘记实现 componentWillUnmount,或者它的逻辑是否没有完全“镜像覆盖到” componentDidMount 的作用。
使用错误边界捕获渲染错误
默认情况下,如果你的应用程序在渲染过程中抛出错误,React 将从屏幕上删除其 UI。为了防止这种情况,你可以将 UI 的一部分包装到 错误边界 中。错误边界是一个特殊的组件,可让你显示一些备用 UI,而不是例如错误消息这样崩溃的部分。
要实现错误边界组件,你需要提供静态 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,并向你的错误报告服务发送生产错误报告。
你不需要将每个组件包装到单独的错误边界中。当你考虑错误边界的布置时,请考虑在哪里显示错误消息才有意义。例如,在消息传递应用程序中,在对话列表周围放置错误边界是有意义的。在每条单独的消息周围放置一个也是有意义的。然而,在每个头像周围设置边界是没有意义的。
备选方案
将简单的类组件迁移为函数式
一般来说,你将组件定义为函数。
例如,假设你要将 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" /> </> ); }
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 variables 来创建一个函数。
import { useState } from 'react';
function Counter() {
const [name, setName] = useState('Taylor');
const [age, setAge] = useState(42);
// ...接下来,转换事件处理程序:
function Counter() {
const [name, setName] = useState('Taylor');
const [age, setAge] = useState(42);
function handleNameChange(e) {
setName(e.target.value);
}
function handleAgeChange() {
setAge(age + 1);
}
// ...最后,将所有以 this 开头的引用替换为你在组件中定义的变量和函数。例如,将 this.state.age 替换为 age,将 this.handleNameChange 替换为 handleNameChange。
这是一个完全转换后的组件:
import { useState } from 'react'; export default function Counter() { const [name, setName] = useState('Taylor'); const [age, setAge] = useState(42); function handleNameChange(e) { setName(e.target.value); } function handleAgeChange() { setAge(age + 1); } return ( <> <input value={name} onChange={handleNameChange} /> <button onClick={handleAgeChange}> Increment age </button> <p>Hello, {name}. You are {age}.</p> </> ) }
import { Component } from 'react'; import { createConnection } from './chat.js'; export default class ChatRoom extends Component { state = { serverUrl: 'https://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>Welcome to the {this.props.roomId} room!</h1> </> ); } }
首先,验证你的 componentWillUnmount 是否与 componentDidMount 执行相反的操作。在上面的示例中操作是正确的:它会断开 componentDidMount 设置的连接。如果缺少这样的逻辑,请先添加它。
接下来, 验证你的 componentDidUpdate 方法是否可以处理对 componentDidMount 中使用的任何 props 和 state 的更改。在上面的例子中,componentDidMount 调用 setupConnection 来读取 this.state.serverUrl 和 this.props.roomId。这就是为什么 componentDidUpdate 检查 this.state.serverUrl 和 this.props.roomId 是否已更改,如果更改了则重置连接。 如果你的 componentDidUpdate 逻辑丢失或无法处理所有相关 props 和 state 的更改,请首先修复该问题。
在上面的示例中,生命周期方法内的逻辑将组件连接到 React 外部的系统(聊天服务器)。要将组件连接到外部系统,将此逻辑描述为单个效果:
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 调用相当于上面生命周期方法中的逻辑。如果你的生命周期方法做了多个互不相关的事,将它们分成多个独立的效果。这是一个你可以使用的完整示例:
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> </> ); }
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> ) }