React生命周期
单个组件的生命周期
挂载
当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
注意:
下述生命周期方法即将过时,在新代码中应该避免使用它们:
UNSAFE_componentWillMount()
更新
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
注意:
下述方法即将过时,在新代码中应该避免使用它们:
UNSAFE_componentWillUpdate()
UNSAFE_componentWillReceiveProps()
卸载
当组件从 DOM 中移除时会调用如下方法:
componentWillUnmount()
错误处理
当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:
static getDerivedStateFromError()
componentDidCatch()
常用的生命周期方法
render
render()
方法是 class 组件中唯一必须实现的方法。
当 render
被调用时,它会检查 this.props
和 this.state
的变化并返回以下类型之一:
- React 元素。通常通过 JSX 创建。例如,
<div />
会被 React 渲染为 DOM 节点,<MyComponent />
会被 React 渲染为自定义组件,无论是<div />
还是<MyComponent />
均为 React 元素。 - 数组或 fragments。 使得 render 方法可以返回多个元素。
- Portals。可以渲染子节点到不同的 DOM 子树中。
- 字符串或数值类型。它们在 DOM 中会被渲染为文本节点
- 布尔类型或
null
。什么都不渲染。(主要用于支持返回test && <Child />
的模式,其中 test 为布尔类型。)
render()
函数应该为纯函数,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。
如需与浏览器进行交互,请在 componentDidMount()
或其他生命周期方法中执行你的操作。保持 render()
为纯函数,可以使组件更容易思考。
注意:如果
shouldComponentUpdate()
返回 false,则不会调用render()
。
constructor(props)
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前调用 super(props)
。否则,this.props
在构造函数中可能会出现未定义的 bug。
通常,在 React 中,构造函数仅用于以下两种情况:
- 通过给
this.state
赋值对象来初始化内部 state。 - 为事件处理函数绑定实例。
在 constructor()
函数中不要调用 setState()
方法。
如果你的组件需要使用内部 state,请直接在构造函数中为 this.state
赋值初始 state:
1 | constructor(props) { |
只能在构造函数中直接为 this.state
赋值。如需在其他方法中赋值,你应使用 this.setState()
替代。
要避免在构造函数中引入任何副作用或订阅。如遇到此场景,请将对应的操作放置在 componentDidMount
中。
注意
避免将 props 的值复制给 state!这是一个常见的错误:
1 | constructor(props) { |
如此做毫无必要(你可以直接使用 this.props.color
),同时还产生了 bug(更新 prop 中的 color
时,并不会影响 state)。
componentDidMount()
componentDidMount()
会在组件挂载后(插入 DOM 树中)立即调用。
依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。
这个方法是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在 componentWillUnmount()
里取消订阅。
你可以在 componentDidMount()
里**直接调用 setState()
**。
它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。
如此保证了即使在 render()
两次调用的情况下,用户也不会看到中间状态。
请谨慎使用该模式,因为它会导致性能问题。
通常,你应该在 constructor()
中初始化 state。如果你的渲染依赖于 DOM 节点的大小或位置,比如实现 modals 和 tooltips 等情况下,你可以使用此方式处理。
componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate()
会在更新后会被立即调用。首次渲染不会执行此方法。
当组件更新后,可以在此处对 DOM 进行操作。
如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。(例如,当 props 未发生变化时,则不会执行网络请求)。
1 | componentDidUpdate(prevProps) { |
你也可以在 componentDidUpdate()
中直接调用 setState()
,但请注意它必须被包裹在一个条件语句里,正如上述的例子那样进行处理,否则会导致死循环。
它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。不要将 props “镜像”给 state,请考虑直接使用 props。
如果组件实现了 getSnapshotBeforeUpdate()
生命周期(不常用),则它的返回值将作为 componentDidUpdate()
的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。
注意:如果
shouldComponentUpdate()
返回值为 false,则不会调用componentDidUpdate()
。
componentWillUnmount()
componentWillUnmount()
会在组件卸载及销毁之前直接调用。
在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount()
中创建的订阅等。
componentWillUnmount()
中不应调用 setState()
,因为该组件将永远不会重新渲染。
组件实例卸载后,将永远不会再挂载它。
不常用的生命周期方法
本节中的生命周期方法并不太常用。它们偶尔会很方便,但是大部分情况下组件可能都不需要它们。
shouldComponentUpdate(nextProps, nextState)
根据 shouldComponentUpdate()
的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。
默认行为是 state 每次发生变化组件都会重新渲染。大部分情况下,你应该遵循默认行为。
当 props 或 state 发生变化时,shouldComponentUpdate()
会在渲染执行之前被调用。返回值默认为 true。
首次渲染或使用 forceUpdate()
时不会调用该方法。
此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。
你应该考虑使用内置的 PureComponent
组件,而不是手动编写 shouldComponentUpdate()
。
PureComponent
会对 props 和 state 进行浅层比较,并减少了跳过必要更新的可能性。
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps
会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
它应返回一个对象来更新 state,如果返回 null
则不更新任何内容。
此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props。例如,实现 <Transition>
组件可能很方便,该组件会比较当前组件与下一组件,以决定针对哪些组件进行转场动画。
派生状态会导致代码冗余,并使组件难以维护。 确保你已熟悉这些简单的替代方案:
- 如果你需要执行副作用(例如,数据提取或动画)以响应 props 中的更改,请改用
componentDidUpdate
。 - 如果只想在 prop 更改时重新计算某些数据,请使用 memoization helper 代替。
- 如果你想在 prop 更改时“重置”某些 state,请考虑使组件完全受控或使用
key
使组件完全不受控 代替。
此方法无权访问组件实例。如果你需要,可以通过提取组件 props 的纯函数及 class 之外的状态,在getDerivedStateFromProps()
和其他 class 方法之间重用代码。
请注意,不管原因是什么,都会在每次渲染前触发此方法。这与 UNSAFE_componentWillReceiveProps
形成对比,后者仅在父组件重新渲染时触发,而不是在内部调用 setState
时。
getSnapshotBeforeUpdate(prevProps, prevState)
getSnapshotBeforeUpdate()
在最近一次渲染输出(提交到 DOM 节点)之前调用。
它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。
此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()
。
此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。
应返回 snapshot 的值(或 null
)。
过时的生命周期方法
以下生命周期方法标记为“过时”。这些方法仍然有效,但不建议在新代码中使用它们。
UNSAFE_componentWillMount()
注意:此生命周期之前名为
componentWillMount
。该名称将继续使用至 React 17。
UNSAFE_componentWillMount()
在挂载之前被调用。
它在 render()
之前调用,因此在此方法中同步调用 setState()
不会触发额外渲染。
通常,我们建议使用 constructor()
来初始化 state。
避免在此方法中引入任何副作用或订阅。如遇此种情况,请改用 componentDidMount()
。
此方法是服务端渲染唯一会调用的生命周期函数。
UNSAFE_componentWillReceiveProps(nextProps)
注意:此生命周期之前名为
componentWillReceiveProps
。该名称将继续使用至 React 17。使用此生命周期方法通常会出现 bug 和不一致性:
- 如果你需要执行副作用(例如,数据提取或动画)以响应 props 中的更改,请改用
componentDidUpdate
生命周期。- 如果你使用
componentWillReceiveProps
仅在 prop 更改时重新计算某些数据,请使用 memoization helper 代替。- 如果你使用
componentWillReceiveProps
是为了在 prop 更改时“重置”某些 state,请考虑使组件完全受控或使用key
使组件完全不受控代替。
UNSAFE_componentWillReceiveProps()
会在已挂载的组件接收新的 props 之前被调用。
如果你需要更新状态以响应 prop 更改(例如,重置它),你可以比较 this.props
和 nextProps
并在此方法中使用 this.setState()
执行 state 转换。
请注意,如果父组件导致组件重新渲染,即使 props 没有更改,也会调用此方法。如果只想处理更改,请确保进行当前值与变更值的比较。
在挂载过程中,React 不会针对初始 props 调用 UNSAFE_componentWillReceiveProps()
。组件只会在组件的 props 更新时调用此方法。调用 this.setState()
通常不会触发 UNSAFE_componentWillReceiveProps()
。
UNSAFE_componentWillUpdate(nextProps, nextState)
注意:此生命周期之前名为
componentWillUpdate
。该名称将继续使用至 React 17。
当组件收到新的 props 或 state 时,会在渲染之前调用 UNSAFE_componentWillUpdate()
。使用此作为在更新发生之前执行准备更新的机会。初始渲染不会调用此方法。
注意,你不能此方法中调用 this.setState()
;在 UNSAFE_componentWillUpdate()
返回之前,你也不应该执行任何其他操作(例如,dispatch Redux 的 action)触发对 React 组件的更新
通常,此方法可以替换为 componentDidUpdate()
。如果你在此方法中读取 DOM 信息(例如,为了保存滚动位置),则可以将此逻辑移至 getSnapshotBeforeUpdate()
中。
注意:如果
shouldComponentUpdate()
返回 false,则不会调用UNSAFE_componentWillUpdate()
。
其他 API
不同于上述生命周期方法(React 主动调用),以下方法是你可以在组件中调用的方法。
只有两个方法:setState()
和 forceUpdate()
。
setState(updater, [callback])
setState()
将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。
这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式。
将 setState()
视为请求而不是立即更新组件的命令。
为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。React 并不会保证 state 的变更会立即生效。
component.forceUpdate(callback)
默认情况下,当组件的 state 或 props 发生变化时,组件将重新渲染。
如果 render()
方法依赖于其他数据,则可以调用 forceUpdate()
强制让组件重新渲染。
调用 forceUpdate()
将致使组件调用 render()
方法,此操作会跳过该组件的 shouldComponentUpdate()
。
但其子组件会触发正常的生命周期方法,包括 shouldComponentUpdate()
方法。如果标记发生变化,React 仍将只更新 DOM。
通常你应该避免使用 forceUpdate()
,尽量在 render()
中使用 this.props
和 this.state
。
Hooks 组件
函数组件 的本质是函数,没有 state 的概念的,因此不存在生命周期一说,仅仅是一个 render 函数而已。
但是引入 Hooks 之后就变得不同了,它能让组件在不使用 class 的情况下使用 state 以及其他的 React特性,相比与 class 的生命周期概念来说,它更接近于实现状态同步,而不是响应生命周期事件。但我们可以利用 useState
、 useEffect()
和 useLayoutEffect()
来模拟实现生命周期。
即:Hooks 组件更接近于实现状态同步,而不是响应生命周期事件。
class 组件 | Hooks /函数式组件 |
---|---|
constructor | useState |
getDerivedStateFromProps | useState 里面 update 函数 |
shouldComponentUpdate | useMemo |
render | 函数本身 |
componentDidMount | useEffect |
componentDidUpdate | useEffect |
componentWillUnmount | useEffect 里面返回的函数 |