React生命周期变迁

120 阅读6分钟

React 15

image.png

四个核心流程

  • 挂载
    • constructor
    • componentWillMount
    • render
    • componentDidMount
  • 更新(父组件触发)
    • componentWillReceiveProps
    • shouldComponentUpdate(true)
    • componentWillUpdate
    • render
    • componentDidUpdate
  • 更新(组件内部触发)
    • shouldComponentUpdate(true)
    • componentWillUpdate
    • render
    • componentDidUpdate
  • 卸载
    • componentWillUnmount

主要函数介绍

  • constructor(props)

  • componentWillMount()

  • componentWillReceiveProps(nextProps)

    • 触发不是因为传递 props 变化,而是父组件只要被 re-render(重渲染),那么子组件的 componentWillReceiveProps 就会被执行
  • shouldComponentUpdate(nextProps, nextState)

    • 返回布尔类型,如果该方法返回值为 false,则可以跳过更新,不执行后续的生命周期方法
  • componentDidMount()

  • componentWillUpdate(nextProps, nextState)

  • componentDidUpdate(prevProps, prevState, snapshot)

    • 传入的不是下一次的属性和状态,而是上一次的。当前的需要从this对象上获取
  • render()

  • componentWillUnmount()

    • 会在组件被销毁时执行
      • 在父组件中被移除
      • 组件被设置了 key 值,父组件在 render 的过程发现 key 与上一次的不一致,那么这个组件也会被销毁,然后被重新初始化,重新设置 key 值

React16.3/React16.4

image.png

四个主要流程

  • 挂载
    • constructor
    • getDerivedStateFromProps(null)
    • render
    • componentDidMount
  • 更新(父组件触发)
    • getDerivedStateFromProps(null)
    • shouldComponentUpdate(true)
    • getSnapshotBeforeUpdate
    • render
    • componentDidUpdate
  • 更新(组件内部触发)
    • getDerivedStateFromProps(null)
    • shouldComponentUpdate(true)
    • getSnapshotBeforeUpdate
    • render
    • componentDidUpdate
  • 卸载
    • componentWillUnmount

主要函数解读

  • constructor(props)

  • static getDerivedStateFromProps(props, state)

    • 目的不是为了替换 componentWillMount,而是为了替换 componentWillReceiveProps。该方法是一个静态方法(static),静态方法不依赖于组件的实例而存在,所以无法在方法内部读取 this 对象,而且它应该返回一个新的对象,或者一个 null 值,它存在的目的有且仅有一个:使用 props 来派生/更新 state,所有不是以此为目标的使用方式原则上来说都是错误的。
    • getDerivedStateFromProps 不仅是在更新阶段会被调用,在挂载阶段也会被调用,这是因为派生 state 的诉求不仅仅在更新时存在,在初始化 state 时也会有需求。通过该方法派生 state 不会引起 render 函数重复执行。
  • shouldComponentUpdate(nextProps, nextState)

  • getSnapshotBeforeUpdate(prevProps, prevState)

    • 提供了一个时机读取当前 DOM 的一些信息,并把返回的值赋值给 componentDidUpdate 方法的 snapshot 参数,主要用来处理 UI 显示,比如某些区域的滚动位置信息等。
  • componentDidMount()

  • componentDidUpdate(prevProps, prevState, snapshot)

  • render()

  • componentWillUnmount()

变化及原因

流程上

  • render 阶段:纯净且没有副作用,可以被 React 暂停,终止或重新启动
  • pre-commit 阶段:可以读取 DOM
  • commit 阶段:可以使用 DOM,运行副作用,安排更新

总体来说就是,render 阶段在执行过程中允许被打断,commit 阶段则总是同步执行。之所以确定这样的标准也是有深入考虑的,在 render 阶段的所有操作一般都是不可见的,所以被重复打断与重新执行,对用户来说是无感知的,在 commit 阶段会涉及到真实 DOM 的操作,如果该阶段也被反复打断重新执行,会导致 UI 界面多次更改渲染,这是绝对要避免的问题。

函数上

  • 废弃 componentWillMount 方法

    • 因为这个方法实在是没什么用
  • 废弃 componentWillReceiveProps 新增 getDerivedStateFromProps

    • 简化派生 state 的代码
    • componentWillReceiveProps 方法仅仅在更新阶段才会被调用,而且在此函数中调用 setState 方法更新 state 会引起额外的 re-render,如果处理不当可能会造成大量无用的 re-render
    • getDerivedStateFromProps是做减法,是 React 在推行只用 getDerivedStateFromProps 来完成 props 到 state 的映射这一最佳实践,确保生命周期函数的行为更加可控可预测,从根源上帮助开发者避免不合理的编程方式,同时也是在为新的 Fiber 架构 铺路。
  • 废弃componentWillUpdate 新增 getSnapshotBeforeUpdate

    • componentWillUpdate 是 Fiber 架构落地的一块绊脚石,不得不废弃掉。
  • 新增 getDerivedStateFromError 与 componentDidCatch 错误处理函数

原因解读

看一下被废弃的生命周期函数:

  • componentWillMount
  • componentWillUpdate
  • componentWillReceiveProps

这些生命周期的共性就是它们都处于 render 阶段,都可能被暂停,终止和重新执行。而如果开发者在这些函数中运行了副作用(或者操作 DOM),那么副作用函数就有可能会被多次重复执行,会带来意料之外的严重 bug。

总结就是:

  • 为 Fiber 架构落地清除障碍,引入增量渲染的机制解决同步渲染引起的应用卡顿风险
  • 以废弃改进 API 的方式避免开发者滥用生命周期函数,推行强制性的最佳实践(每一个值有且仅有一个明确的来源)

Fiber架构

  • 原因:React v16 之前,每触发一次组件的更新,都会构建一棵新的虚拟 DOM 树,通过与上一次的虚拟 DOM 树进行 Diff 比较,实现对真实 DOM 的定向更新。这一整个过程是递归进行的(想想 React 应用的组织形式),而同步渲染的递归调用栈层次非常深(代码写得不好的情况下非常容易导致栈溢出),只有最底层的调用返回,整个渲染过程才会逐层返回。这个漫长的更新过程是不可中断的,同步渲染一旦开始,主线程(JavaScript 解析与执行)会一直被占用,直到递归彻底完成,在此期间浏览器没有办法处理任何渲染之外的事情(比如说响应用户事件)。这个问题对于大型的 React 应用来说是没办法接受的。

  • 原理:Fiber 会将一个大的更新任务拆解为许多个小任务。每一个小任务执行完成后,渲染进程会把主线程交回去(释放),看看有没有其它优先级更高的任务(用户事件响应等)需要处理,如果有就执行高优先级任务,如果没有就继续执行其余的小任务。通过这样的方式,避免主线程被长时间的独占,从而避免应用卡顿的问题。这种可以被打断的渲染过程就是所谓的异步渲染。

  • 特点:Fiber 带来了两个重要的特性:任务拆解 与 渲染过程可打断。关于可打断并不是说任意环节都能打断重新执行,可打断的时机也是有所区分的。根据能否被打断这一标准,React v16 的生命周期被划分为了 render 和 commit 两个阶段(commit 又被细分为 pre-commit 和 commit)。

React16.8

主要变化

新增Hooks。Hooks 能够让函数组件拥有使用与管理 state 的能力,也就演化出了函数组件生命周期的概念(render 之外新增了其他过程),涉及到的 Hook 主要有几个:useStateuseMemouseEffect

Hooks 与 生命周期函数对应关系:

image.png

原因解读

  • 复用:便于分离与复用组件的状态逻辑(Mixin,高阶组件,渲染回调模式等)
  • 副作用:复杂组件变得难以理解(状态与副作用越来越多,生命周期函数滥用)
  • this: 类组件中难以理解的 this 指向(bind 语法)
  • 优化:类组件难以被进一步优化(组件预编译,不能很好被压缩,热重载不稳定)

参考资料: