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)
      1. getDerivedStateFromProps 是一个静态方法. 静态方法不依赖组件的实例而存在,因此在其内部无法访问 this 属性
      1. 该方法接收两个参数: propsstate, 他们分别代表当前接收到的父组件的props自身的state
      1. 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的所有用例.
    1. getDerivedStateFromProps 是作为一个试图代替 componentWillReceiveProps 的 API 而出现的;
    1. getDerivedStateFromProps 不能完全和 componentWillReceiveProps 画等号,其特性决定了我们曾经在 componentWillReceiveProps 里面做的事情,不能够百分百迁移到 getDerivedStateFromProps 里.
    1. 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个生命周期

  • componentWillMount
  • componentWillUpdate
  • componentWillReceiveProps
  • 这些生命周期的共性, 就是它们都处于 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 下也有不少人能把自己“玩死”.

比如在 componentWillReceivePropscomponentWillUpdate 里滥用 setState 导致重复渲染死循环的

posted @ 2020-10-22 21:56  荣光无限  阅读(198)  评论(0)    收藏  举报