componentWillReceiveProps
一、认识
componentWillReceiveProps
是 React
类组件中的一个旧生命周期方法,它在组件接收到新的 props
(但在组件更新前)调用,用来对比新的 props
与当前 props
,并据此决定是否需要更新组件的 state
。在更新组件阶段,该生命周期执行驱动是因为父组件更新带来的 props
修改,但是只要父组件触发 render
函数,调用 React.createElement
方法,那么 props
就会被重新创建,生命周期 componentWillReceiveProps
就会执行了。这就解释了即使 props
没变,该生命周期也会执行
React Fiber
架构实现了 Fiber
将渲染模式从原来 不可中断的同步任务改为可中断的异步任务, 任务单元之间的任意切换和任务之间的打断和恢复等等。Fiber
架构下的异步渲染, 导致了 componentWillMount
、componentWillReceiveProps
、 componentWillUpdate
三个生命周期在实际渲染之前可能会被调用多次,产生不可预料的调用结果。因此这三个生命周期函数不建议被使用,取而代之的是使用全新的两个生命周期函数 getDerivedStateFromProps
和 getSnapshotBeforeUpdate
React Fiber
架构下, 基于 Schedule
调度之后, 开始进行 Render
调和 和 Commit
提交。Render
调和阶段 的主要目标是生成一棵新的 Fiber
树,并为后续的 DOM
更新准备好 副作用列表(effect list
)。这个阶段不直接操作 DOM
,而是进行计算和比较。render
阶段以 DFS
深度优先 的顺序遍历 ReactElement
, 如果有子节点, 遍历子节点, 如果没有子节点遍历兄弟节点。Render
阶段 可以中断和恢复。Commit
提交阶段 是 React
更新流程中唯一会直接操作 DOM
的阶段,它根据 Render
阶段生成的副作用列表执行实际的更新操作。这个阶段是同步执行的,确保所有副作用在一次提交中完成, 这个阶段是同步且不可中断的,确保 UI
的最终状态与最新的 React
元素描述完全一致。简单来说,由于 Render
阶段的操作对用户来说其实是不可见的,所以就算打断再重启,对用户来说也是零感知。而 commit
阶段的操作则涉及真实 DOM
的渲染,所以这个过程必须用同步渲染来求稳。
由于 Render
阶段 可以中断和恢复, 因此, 这就导致 render
阶段的生命周期都是有可能被重复执行的。componentWillMount
、componentWillReceiveProps
和 componentWillUpdate
都是在渲染阶段调用的,而不是在提交阶段调用。因此,如果渲染过程被中断或重启,这些方法就有可能被执行多次。由于这些方法会重复调用,如果其中包含了副作用操作(例如直接修改组件状态、调用 API
等),就可能导致意外的行为或性能问题。这也是为什么 React
官方建议尽量避免使用这些生命周期方法,并将它们标记为“不安全”(UNSAFE_
)。
那么, 为什么 getDerivedStateFromProps
和 getSnapshotBeforeUpdate
可以呢?
-
getDerivedStateFromProps
, 它在Render
阶段调用,每次组件更新(包括挂载和更新)时都会在render
之前被调用,用来根据新的props
计算并返回新的state
。它是一个静态方法,不依赖于组件实例,因此无法直接访问或修改实例属性,也就不会产生副作用。它根据当前props
和state
计算出新的state
,只是返回一个对象供React
合并,完全不会触发额外的副作用。即使在并发模式下render
可能会被中断或重复调用,由于它是纯函数,多次调用不会对组件产生不一致的影响。 -
getSnapshotBeforeUpdate
, 在render
之后、DOM
更新之前调用,用于捕获(snapshot
)一些即将更新的DOM
信息。捕获的信息会传递给componentDidUpdate
,以便在DOM
更新后使用。它同样不应引入副作用,只用于读取数据。它只读取并返回数据,而不会直接引入副作用, 没有引起状态或数据的直接修改。由于其仅用于 读取 操作,多次调用也不会对最终结果产生负面影响。