重新学习 React (一) 生命周期,Fiber 调度和更新机制

栏目: IOS · Android · 发布时间: 5年前

内容简介:React 有一套合理的运行机制去控制程序在指定的时刻该做什么事,当一个生命周期钩子被触发后,紧接着会有下一个钩子,直到整个生命周期结束。一旦有多个组件(比如组件和它的子组件)触发生命周期钩子,就会出现先后问题,而调度是为了安排和优化生命周期钩子的执行顺序问题。首先看一下 React V16.4 后的生命周期概况(图片来源)关于 Fiber,强烈建议听一下知乎上程墨Morgan的 live 《深入理解React v16 新功能》,这里的图片也是引用于此 live

React 有一套合理的运行机制去控制程序在指定的时刻该做什么事,当一个生命周期钩子被触发后,紧接着会有下一个钩子,直到整个生命周期结束。一旦有多个组件(比如组件和它的子组件)触发生命周期钩子,就会出现先后问题,而调度是为了安排和优化生命周期钩子的执行顺序问题。

React 生命周期详解

首先看一下 React V16.4 后的生命周期概况(图片来源)

重新学习 React (一) 生命周期,Fiber 调度和更新机制
  • 从横向看,react 分为三个阶段:
    • 创建时
      constructor()
      static getDerivedStateFromProps()
      render()
      componentDidMount()
      
    • 更新时
      static getDerivedStateFromProps()
      shouldComponentUpdate()
      render()
      getSnapshotBeforeUpdate()
      componentDidUpdate()
      
    • 卸载时
      • componentWillUnmount() - 组件销毁之前被直接调用

敲黑板,重点来了,从上面的分析可以知道:

  • 有三种方式可以触发 React 更新,props 发生改变,调用 setState() 和调用 forceUpdate() 方法
  • static getDerivedStateFromProps() 这个钩子会在每个更新操作之前(即使props没有改变)执行一次,使用时应该保持谨慎。
  • componentDidMount()componentDidUpdate() 执行的时机是差不多的,都在 render 之后只不过前者只在首次渲染后执行,后者首次渲染不会执行
  • getSnapshotBeforeUpdate() 此时可以获得只读的新 DOM 树,然后传给 componentDidUpdate(prevProps, prevState, snapshot)

尝试理解 Fiber

关于 Fiber,强烈建议听一下知乎上程墨Morgan的 live 《深入理解React v16 新功能》,这里的图片也是引用于此 live

重新学习 React (一) 生命周期,Fiber 调度和更新机制

我们知道 React 是通过递归的方式来渲染组件的,在 V16 版本之前的版本里,当一个状态发生变更时,react 会从当前组件开始,依次递归调用所有的子组件生命周期钩子,而且这个过程是 同步执行 的且 无法中断 的,如上次图所示,一旦有很深很深的组件嵌套,就会造成严重的页面卡顿,影响用户体验。

React 在V16版本之前的版本里引入了 Fiber 这样一个东西,它的英文涵义为纤维,在计算机领域它排在在进程和线程的后面,虽然 React 的 Fiber 和计算机调度里的概念不一样,但是可以方便对比理解,我们大概可以想象到 Fiber 可能是一个比线程还小的时间片段。

重新学习 React (一) 生命周期,Fiber 调度和更新机制

Fiber 会当前需要渲染的任务分成一个个微任务,安排优先级,然后依次处理,每过一段时间(非常短,毫秒级)就会暂停当前的任务,查看优先级最高的然后暂停(也可能会完全放弃)掉之前的执行结果,跳出到下一个微任务,如上图所示。同时 Fiber 还做了一些优化,可以保持住之前运行的结果以到达复用目的。

从上面的图片可以看出,Fiber 将整个生命周期分成了三个阶段:

  • render 阶段
    • 由于 Fiber 会时不时跳出任务,然后重新执行,会导致该阶段的生命周期调用多次的现象,所以 React V16 之前 componentWillMount()componentWillUpdate()componentWillReceiveProps() 的三个生命周期钩子被加上了 UNSAFE 标记
    • 这个阶段效率不一定会比之前同步递归来的快,因为会有任务跳出重做的性能损耗,但是从宏观上看,它不断执行了最高优先级(影响用户使用体验)的任务,所以用户使用起来会比以前更加的流畅
    • 这个阶段的生命周期钩子可能会重复调用,建议只写无副作用的代码
  • pre-commit 阶段
    getSnapshotBeforeUpdate()
    
  • commit 阶段
    • 此时的 DOM 可以进行操作
    • 这个阶段组件已经完成更新,可以写一些有副作用的代码

其它生命周期钩子

除了上面常用的钩子,React 还提供了如下钩子:

static getDerivedStateFromError()
componentDidCatch()

更新机制

理解了生命周期和三个执行阶段,就可以比较容易理解组件状态的更新机制了。

setState()

这个方法可以让我们更新组件的 state 状态。第一个参数可以是对象,也可以是 updater 函数,如果是函数,则会接受当前的 state 和 props 作为参数。第二个参数为函数,是在 commit 阶段后执行,准确的说是在 componentDidUpdate() 后执行。

setState() 的更新过程是异步的(除非绑定在 DOM 事件中或写在 setTimeout 里),而且会在最后合并所有的更新,如下:

Object.assign(
  previousState,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)
复制代码

之所以设计成这样,是为了避免在一次生命周期中出现多次的重渲染,影响页面性能。

forceUpdate()

如果我们想强制刷新一个组件,可以直接调用该方法,调用时会直接执行 render() 这个函数而跳过 shouldComponentUpdate()

结语

了解 react 生命周期和更新机制确实有利于编写代码,特别是当代码量越来越大时,错用的 setState 或生命周期钩子都可能埋下越来越多的雷,直到有一天无法维护。。。

我的个人建议如下:

  • 把副作用代码通通放在 commit 阶段,因为这个阶段不会影响页面渲染性能
  • 尽可能不要使用 forceUpdate() 方法,借用 Evan You 的一句话,如果你发现你自己需要在 Vue 中做一次强制更新,99.9% 的情况,是你在某个地方做错了事
  • 只要调用了 setState() 就会进行 render(),无论 state 是否改变
  • 知道 setState() 更新的什么时候是同步的,什么时候是异步的,参见上文
  • 不要把 getDerivedStateFromProps() 当成是 UNSAFE_componentWillReceiveProps() 的替代品,因为 getDerivedStateFromProps() 会在每次 render() 之前执行,即使 props 没有改变

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

用户体验面面观

用户体验面面观

[美] 库涅夫斯基(Mike Kuniavsky) / 汤海 / 清华大学出版社 / 2010-5 / 69.00

这是一本专注于用户研究和用户体验的经典读物,同时也是一本容易上手的实战手册,从实践者的角度,着重讨论和阐述了用户研究的重要性、主要的用户研究方法和工具,同时借助于实例介绍了相关的应用。全书共3部分18章,深度剖析了何为优秀的用户设计,用户体验包括哪些研究方法和工具,如何 得出和分析用户体验调查结果等。一起来看看 《用户体验面面观》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具