内容简介:React 有一套合理的运行机制去控制程序在指定的时刻该做什么事,当一个生命周期钩子被触发后,紧接着会有下一个钩子,直到整个生命周期结束。一旦有多个组件(比如组件和它的子组件)触发生命周期钩子,就会出现先后问题,而调度是为了安排和优化生命周期钩子的执行顺序问题。首先看一下 React V16.4 后的生命周期概况(图片来源)关于 Fiber,强烈建议听一下知乎上程墨Morgan的 live 《深入理解React v16 新功能》,这里的图片也是引用于此 live
React 有一套合理的运行机制去控制程序在指定的时刻该做什么事,当一个生命周期钩子被触发后,紧接着会有下一个钩子,直到整个生命周期结束。一旦有多个组件(比如组件和它的子组件)触发生命周期钩子,就会出现先后问题,而调度是为了安排和优化生命周期钩子的执行顺序问题。
React 生命周期详解
首先看一下 React V16.4 后的生命周期概况(图片来源)
- 从横向看,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 是通过递归的方式来渲染组件的,在 V16 版本之前的版本里,当一个状态发生变更时,react 会从当前组件开始,依次递归调用所有的子组件生命周期钩子,而且这个过程是 同步执行 的且 无法中断 的,如上次图所示,一旦有很深很深的组件嵌套,就会造成严重的页面卡顿,影响用户体验。
React 在V16版本之前的版本里引入了 Fiber 这样一个东西,它的英文涵义为纤维,在计算机领域它排在在进程和线程的后面,虽然 React 的 Fiber 和计算机调度里的概念不一样,但是可以方便对比理解,我们大概可以想象到 Fiber 可能是一个比线程还小的时间片段。
Fiber 会当前需要渲染的任务分成一个个微任务,安排优先级,然后依次处理,每过一段时间(非常短,毫秒级)就会暂停当前的任务,查看优先级最高的然后暂停(也可能会完全放弃)掉之前的执行结果,跳出到下一个微任务,如上图所示。同时 Fiber 还做了一些优化,可以保持住之前运行的结果以到达复用目的。
从上面的图片可以看出,Fiber 将整个生命周期分成了三个阶段:
- render 阶段
- 由于 Fiber 会时不时跳出任务,然后重新执行,会导致该阶段的生命周期调用多次的现象,所以 React V16 之前
componentWillMount()
,componentWillUpdate()
,componentWillReceiveProps()
的三个生命周期钩子被加上了UNSAFE
标记 - 这个阶段效率不一定会比之前同步递归来的快,因为会有任务跳出重做的性能损耗,但是从宏观上看,它不断执行了最高优先级(影响用户使用体验)的任务,所以用户使用起来会比以前更加的流畅
- 这个阶段的生命周期钩子可能会重复调用,建议只写无副作用的代码
- 由于 Fiber 会时不时跳出任务,然后重新执行,会导致该阶段的生命周期调用多次的现象,所以 React V16 之前
- 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 没有改变
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。