上一篇文章详解 React15 生命周期中,我们从挂载、更新、卸载三个阶段来介绍 React 15 的生命周期。本篇我们也将同样从这三个阶段来介绍 React16 的生命周期并对比 React 15 和 React 16 两个版本的差异。
React 16 基于 React 16.3 版本。
挂载阶段(Mounting):组件的初始化渲染
从上图可以得知,在挂载阶段中,React 15 和 React 16.3 生命周期的差异是 componentWillMount 替换为 getDerivedStateFromProps。换句话说,废弃了 componentWillMount,新增了 getDerivedStateFromProps。
认识 getDerivedStateFromProps
该生命周期的调用方式:
static getDerivedStateFromProps(props, state)
该生命周期会在调用 render 方法之前调用,并且在初始挂载和后续更新时都会被调用。
示例如下:
import React from "react";
export default class App extends React.Component {
static getDerivedStateFromProps(state, props) {
console.log('getDerivedStateFromProps()');
}
render() {
console.log('render()');
return (
<div>
<h1>Hello World</h1>
</div>
);
}
}
在使用上,有三点需要注意。
第一点,getDerivedStateFromProps 是个静态方法。静态方法不依赖组件实例而存在,所以此方法内部是获取不到 this 的。 如若强行获取 this 则会报错,如下图所示:
第二点,该方法可以接受两个参数:props 和 state。它们分别代表来自父组件的 props 和自身的 state。
第三点,该方法应返回一个对象来更新 state,如果返回 null 则不更新任何内容。若不返回任何内容,React 将会给出警告,如下图所示:
该方法对 state 的更新和 setState 一样,不是“覆盖式”的更新,是针对某个属性的定向更新。
修改我们的示例代码如下:
import React from "react";
export default class App extends React.Component {
state = {
message: "Hello World",
};
static getDerivedStateFromProps(state, props) {
console.log("getDerivedStateFromProps()");
return {
name: "chen",
};
}
componentDidMount() {
console.log("state", this.state);
}
render() {
console.log("render()");
return (
<div>
<h1>Hello World</h1>
</div>
);
}
}
输出如下:
关于 getDerivedStateFromProps() 的更多介绍请访问官网文档。
更新阶段(Updating):组件的更新
在此阶段也新增了一个生命周期:getSnapshotBeforeUpdate 。
认识 getSnapshotBeforeUpdate
该生命周期的调用方式:
getSnapshotBeforeUpdate(prevProps, prevState)
该生命周期在最近一次渲染输出(提交到 DOM 节点)之前调用,即在 render 之后,componentDidUpdata 之前。它可以让我们获取到组件更新前的真实 DOM 和 更新前后的 state&props 信息。
值得注意的一点是,getSnapshotBeforeUpdate 的返回值会作为 componentDidUpdate 的第三个参数。
componentDidUpdate(prevProps, prevState, snapShot)
关于 getSanpshotBeforeUpdate() 的示例和更多介绍请访问官网文档
卸载阶段(Unmounting):组件的卸载
React 16.3 的卸载阶段与 React 15 相同,这里就不再赘述。
过时和新增的生命周期
由上述三个阶段的对比,我们可以知道 React 16.3 对比 React 15 版本哪些生命周期过时,又新增了哪些生命周期。
过时的生命周期
过时的组件生命周期往往会带来不安全的编码实践,具体生命周期如下:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
这些生命周期方法经常被误解和滥用,另外,它们在异步渲染中的危害或许会更大。因此 React 官方为这些生命周期添加了 “UNSAFE_” 前缀。(此处的 “unsafe” 不是指安全性,而是表示这些生命周期在 React 的未来版本中更有可能出现 bug,特别是在启用异步渲染之后)
新增的生命周期
- getDerivedStateFromProps
- getSnapshotBeforeUpdate
getDerivedStateFromProps 与 componentDidUpdate 一起,可以覆盖过时的 componentWillReceiveProps 的所有用例。
getSnapshotBeforeUpdate 与 componentDidUpdate 一起,可以覆盖过时的 componentWilUpdate 的所有用例。
React 16 为什么更改生命周期?
Fiber 架构简析
Fiber 是 React 16 对 React 核心算法的一次重写。关于 Fiber,我们现在只需知道:Fiber 会使 React 15 的递归的无法中断的更新变为异步的可中断更新 。
在具体介绍两者的区别前,先看下两张图:
React 15:
React 16:
在 React 16 之前,更新由于递归执行,所以一旦开始,中途就无法中断。当层级很深时,渲染时间会很长,页面就会面临卡顿甚至卡死的风险,影响用户体验。
而React 16 引入的 Fiber 架构就是解决这个问题的。在更新过程中,不再“一去不回头”,而是可以被打断的,这就是所谓的异步渲染 。
换个角度看生命周期
Fiber 架构的重要特征是可以中断的异步渲染模式。根据是否可以中断,React 16 的生命周期被划分为了 render 和 commit 两个阶段,而 commit 阶段又被细分为 pre-commit 和 commit。各个阶段所包含的生命周期如下图所示:
三个阶段的特征:
- render 阶段:纯净且不包含副作用,可能会被 React 暂停、中止或重新启动。
- pre-commit 阶段:可以读取DOM。
- commit 阶段:可以使用 DOM,运行副作用,安排更新。
总的来说,render 阶段在执行过程中可以中断,而 commit 阶段则总是同步执行的。
render 阶段可以被 React 暂停、中止或重新启动,所以此阶段的生命周期都是有可能会被重复执行的。
或许大家会发现,上文我们提到的过时生命周期,都是在 render 阶段中的,因此这些生命周期都是可能重复执行的。而我们又很喜欢在这些以“componentWill”开头的生命周期中书写很多“骚操作”,包括但不限于:
- setState()
- 发起异步请求
- 操作真实 DOM
这些操作在异步渲染中更有可能会导致重大 bug,所以 React 官方将这些生命置为 “UNSAFE_”的原因。
总结
本篇文章我们从三个阶段对比了 React 15 和 React 16.3 的区别,找出了过时和新增的生命周期并介绍了新增生命周期的用法。
我们又从架构方面分析 React 15 到 React 16.3 的改变,并对 React 生命周期的变化和背后的原因有了新角度的理解。
总的来说,React 16 改变生命周期的主要原因是为了配合 Fiber 架构带来的异步渲染机制。在 React 新版本中,针对生命周期中长期被滥用和误解的部分进行了强制性的最佳实践,确保了Fiber 架构下数据和视图的安全性和正确性,同时也确保了生命周期方法的行为更加纯碎、可控、可预测。(摘自《深入浅出搞定 React》)