React essence note - 3 - 为什么 React 16 要更改组件的生命周期?(下)
为什么 React 16 要更改组件的生命周期?(下)

💛 挂载阶段(Mounting)

-
执行
constructor函数, 可以初始化state, 可以改变函数的this指向 -
constructor函数执行结束, 执行getDerivedStateFromProps生命周期函数(如果当前组件的状态取决于父组件的状态) -
getDerivedStateFromProps(params1,params2)有2个参数, 第1个参数通过props传递过来的父组件的state,第2个参数是当前组件的state,我们可以根据这个2个参数决定要不要更新当前组件的state, 如果不需要更新return null, 如果需要更新return new {} -
getDerivedStateFromProps函数执行接收后, 执行render方法 -
render方法执行结束, 执行componentDidMount方法 ======> 当前组件挂载完成 -
废弃 componentWillMount
-
新增 getDerivedStateFromProps
-
💛💛💛💛 getDerivedStateFromProps 💛💛💛💛 -
getDerivedStateFromProps不是componentWillMount的替代品
-
getDerivedStateFromProps试图替换掉的是componentWillReceiveProps,有且只有一个用途: 使用props来派生/更新state
static getDerivedStateFromProps(props, state)
-
-
getDerivedStateFromProps是一个静态方法. 静态方法不依赖组件的实例而存在,因此在其内部无法访问this属性
-
-
-
- 该方法接收两个参数:
props和state, 他们分别代表当前接收到的父组件的props和自身的state
- 该方法接收两个参数:
-
-
-
getDerivedStateFromProps需要一个对象格式的返回值.
-
-
getDerivedStateFromProps的返回值不可或缺, 是因为React需要用这个返回值来更新(派生)组件的state,因此当我们确实不存使用props派生State这个需求的时候,最好直接省略掉这个生命周期
-
getDerivedStateFromProps方法对state的更新动作并非覆盖式的更新,而是针对某个属性的定向更新. 如果state中有这个属性就更新,如果没有这个属性直接在state里新增,原属性与新属性共存
💛 更新阶段(Updating)

-
React16.4的挂载和更新流程与React16.3保持一致.差异在于更新流程上 -
React16.4中任何因素触发的组件更新流程,都会触发getDerivedStateFromProps -
React16.3中,只有父组件的更新才会触发该声明周期 -
当组件中的数据发生更新之后, 执行
getDerivedStateFromProps函数, 这个函数的功能是替换之前版本的~~componentWillReceiveProps~~这个生命周期函数 -
当
getDerivedStateFromProps执行结束后,开始执行shouldComponentUpdate函数 -
shouldComponentUpdate函数需要返回一个✅true或者 ❌false, -
❌
return false=> 停止更新组件 -
✅
return true=> 执行render方法,重新渲染组件.render方法执行结束后会执行getSnapshotBeforeUpdate生命周期函数
为什么要用 getDerivedStateFromProps 代替 componentWillReceiveProps?
- 官方: 与
componentDidUpdate一起, 这个新得声明周期涵盖过时componentWillReceiveProps的所有用例. -
getDerivedStateFromProps是作为一个试图代替componentWillReceiveProps的 API 而出现的;
-
getDerivedStateFromProps不能完全和componentWillReceiveProps画等号,其特性决定了我们曾经在componentWillReceiveProps里面做的事情,不能够百分百迁移到getDerivedStateFromProps里.
-
getDerivedStateFromProps这个 API,它相对于早期的componentWillReceiveProps来说,正是做了“合理的减法”.而做这个减法的决心之强烈,从getDerivedStateFromProps直接被定义为static方法这件事上就可见一斑——static方法内部拿不到组件实例的this,这就导致你无法在getDerivedStateFromProps里面做任何类似于 ·、不合理的this.setState(会导致死循环的那种)这类可能会产生副作用的操作
- 💛💛💛💛 =====> 原则:
React 16 在强制推行“只用 getDerivedStateFromProps 来完成 props 到 state 的映射”这一最佳实践
消失的 componentWillMount 与新增的 getSnapshotBeforeUpdate
getSnapshotBeforeUpdate方法会在render之后,组件完成更新(componentDidiUpdate)之前执行, 用于执行某种逻辑或计算, 返回值可以在componentDidiUpdate方法中的第3个参数中获取,(我们可以同时获取到更新前的真实 DOM 和更新前后的 state&props 信息), 就是说在组件更新之后拿到这个值再去做其他的事情
// 组件更新时调用
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log("getSnapshotBeforeUpdate方法执行");
return "haha";
}
// 组件更新后调用
componentDidUpdate(nextProps, nextState, valueFromSnapshot) {
console.log("componentDidUpdate方法执行");
console.log("从 getSnapshotBeforeUpdate 获取到的值是", valueFromSnapshot);
}
- getSnapshotBeforeUpdate 配合 componentDidUpdate 使用 替代 componentWillUpdate的所有用例
- getSnapshotBeforeUpdate 要想发挥作用,离不开 componentDidUpdate 的配合
💛 卸载阶段(Unmounting)
- 调用
componentWillUnmount生命周期函数,表示此组件将被卸载,清理一些事件,清理ref等操作
初识Fiber
-
同步渲染过程.
![]()
-
同步渲染的递归调用栈是非常深的,只有最底层的调用返回了,整个渲染过程才会开始逐层返回.这个漫长且不可打断的更新过程,将会带来用户体验层面的巨大风险:同步渲染一旦开始,便会牢牢抓住主线程不放,直到递归彻底完成.在这个过程中,浏览器没有办法处理任何渲染之外的事情,会进入一种无法处理用户交互的状态.因此若渲染时间稍微长一点,页面就会面临卡顿甚至卡死的风险.
-
Fiber 会使原本同步的渲染过程变成异步的.
![]()
-
而 React 16 引入的 Fiber 架构,恰好能够解决掉这个风险:Fiber 会将一个大的更新任务拆解为许多个小任务.每当执行完一个小任务时,渲染线程都会把主线程交回去,看看有没有优先级更高的工作要处理,确保不会出现其他任务被“饿死”的情况,进而避免同步渲染带来的卡顿.在这个过程中,渲染线程不再“一去不回头”,而是可以被打断的,这就是所谓的“异步渲染”.
-
任务拆解&&可打断
再看生命周期

-
render阶段:纯净且没有副作用,可能会被React暂停、终止或重新启动. -
pre-commit阶段:可以读取DOM. -
commit阶段:可以使用DOM,运行副作用,安排更新.
总的来说,render 阶段在执行过程中允许被打断,而 commit 阶段则总是同步执行的 -
💛 简单来说,由于
render阶段的操作对用户来说其实是“不可见”的,所以就算打断再重启,对用户来说也是零感知.而commit阶段的操作则涉及真实 DOM 的渲染,再狂的框架也不敢在用户眼皮子底下胡乱更改视图,所以这个过程必须用同步渲染来求稳
细说生命周期“废旧立新”背后的思考
在 Fiber 机制下, render 阶段是允许暂停、终止和重启的.当一个任务执行到一半被打断后,下一次渲染线程抢回主动权时,这个任务被重启的形式是“重复执行一遍整个任务”而非 接着上次执行到的那行代码往下走.这就导致 render 阶段的生命周期都是有可能被重复执行的.
被 React16 废弃的3个生命周期
componentWillMountcomponentWillUpdatecomponentWillReceiveProps- 这些生命周期的共性, 就是它们都处于 render 阶段, 都可能重复被执行, 而且由于这些 API 常年被滥用, 它们在重复执行的过程中都存在着不可小觑的风险.
在 componentWill开头的生命周期做的那些错事
- ❌ setState()
- ❌fecth发起异步请求
- ❌操作真实DOM
解决方法:
(1) ✅ 完全可以转移到其他生命周期(尤其是 componentDidxxx)里去做.
比如在 componentWillMount 里发起异步请求.很多同学因为太年轻, 以为这样做就可以让异步请求回来得“早一点”, 从而避免首次渲染白屏.
可惜你忘了, 异步请求再怎么快也快不过(React 15 下)同步的生命周期.componentWillMount 结束后, render 会迅速地被触发, 所以说首次渲染依然会在数据返回之前执行.这样做不仅没有达到你预想的目的, 还会导致服务端渲染场景下的冗余请求等额外问题, 得不偿失.
(2) ✅ 在 Fiber 带来的异步渲染机制下, 可能会导致非常严重的 Bug.
试想, 假如你在 componentWillxxx 里发起了一个付款请求.由于 render 阶段里的生命周期都可以重复执行, 在 componentWillxxx 被打断 + 重启多次后, 就会发出多个付款请求.
比如说, 这件商品单价只要 10 块钱, 用户也只点击了一次付款.但实际却可能因为 componentWillxxx 被打断 + 重启多次而多次调用付款接口, 最终付了 50 块钱;又或者你可能会习惯在 componentWillReceiveProps 里操作 DOM(比如说删除符合某个特征的元素), 那么 componentWillReceiveProps 若是执行了两次, 你可能就会一口气删掉两个符合该特征的元素.
结合上面的分析, 我们再去思考 getDerivedStateFromProps 为何会在设计层面直接被约束为一个触碰不到 this 的静态方法, 其背后的原因也就更加充分了——避免开发者触碰 this, 就是在避免各种危险的骚操作.
(3)✅ 即使你没有开启异步, React 15 下也有不少人能把自己“玩死”.
比如在 componentWillReceiveProps 和 componentWillUpdate 里滥用 setState 导致重复渲染死循环的



浙公网安备 33010602011771号