react进阶 react渲染原理

渲染原理

React元素:

React Element,通过React.createElement创建(语法糖:JSX)

React节点:

专门用于渲染到UI界面的对象,React会通过React元素创建React节点
ReactDOM一定时通过React节点来进行渲染的

节点类型

  • React DOM节点(ReactDOMComponent):创建该节点的React元素是一个字符串
  • React 组件节点:创建该节点的React元素类型是一个函数或是一个类
  • React 文本节点:由字符串、数字创建
  • React 空节点:由null、undefined、false、true创建
  • React 数组节点:由一个数组创建

真实DOM

通过document.createElement创建的dom元素

首次渲染(新节点渲染)

  1. 根据参数的值创建节点
  2. 不同节点的操作有异
  • 文本节点:通过document.createTextNode创建真实的文本节点
  • 空节点:什么都不做
  • 数组节点:遍历数组,将数组每一项递归创建节点
  • DOM节点:通过document.createElement创建真实的DOM对象,然后立即设置该真实DOM元素的各种属性,遍历React元素的children属性,递归创建节点
  • 组件节点:
    • 函数组件:调用函数(该函数必须返回一个可以生成节点的内容),将该函数的返回结果递归创建节点
    • 类组件:
      1. 创建该类的实例
      2. 立即调用对象的生命周期方法:static getDerivedStateFromProps
      3. 运行该对象的render方法,拿到节点对象(递归创建节点)
      4. 将该组件的componentDidMount加入到执行队列,在整个虚拟DOM树全部构建完毕,并且将真实的DOM对象加入到容器中后,执行该队列
  1. 生成出虚拟DOM树,将该树保存起来
  2. 将之前生成的真实DOM对象加入到容器中

更新节点

更新的场景

  1. 重新调用ReactDOM.render,完全重新生成节点树
    • 触发根节点更新
  2. 在类组件的实例对象中调用setSate ,会导致该实例所在的节点更新

节点的更新

  • 如果调用ReactDOM.render,进入根节点对比更新
  • 如果调用setState
    1. 运行生命周期函数 static getDerivedStateFromProps
    2. 运行生命周期函数shouldComponentUpdate,如果该函数返回false,终止当前流程
    3. 运行render得到一个新的节点,进入该新节点的对比更新
    4. 将生命周期函数getSnapshotBeforeUpdate加入执行队列,等待将来执行
    5. 将生命周期函数componentDidUpdate加入执行队列,等待将来执行

后续步骤:(两种情况都要执行)

  1. 更新虚拟DOM树
  2. 完成真实的DOM更新
  3. 依次调用执行队列中的componentDidMount函数
  4. 依次调用执行队列中的getSnapshotBeforeUpdate函数
  5. 依次调用执行队列中的componentDidUpdate函数

对比更新

将新产生的节点,对比之前虚拟DOM中的节点,发现差异,完成更新。

React为了提高对比效率,做出如下假设

  1. 假设节点不会出现层次的移动
  2. 不同的节点类型会生成不同的结构
    • 相同的节点类型:节点本身类型相同,如果由react元素生成,type值必须一致
  3. 多个兄弟节点通过唯一标识(key)来确定对比的新节点

key值的作用:用于通过旧节点,找到对应的新节点,如果某个旧节点有key值,则其更新时,会寻找相同层级中相同key值的节点进行对比。

key值应该在一个范围内唯一(兄弟节点中),并且保持稳定

找到了对比的目标
判断节点类型是否一致
  • 一致
  1. 根据不同的节点做不同的事情
    • 空节点:不做任何事
    • DOM节点:
      1. 直接使用之前的真实DOM对象
      2. 将其属性的变化记录下来,等待将来统一完成更新(现在不会做真正的变化)
      3. 遍历该新React元素的子元素,递归对比更新
    • 文本节点:
      1. 直接使用之前的真实DOM对象
      2. 将其nodeValue的变化记录下来,等待将来统一完成更新(现在不会做真正的变化)
    • 组件节点:
      • 函数组件:直接重新调用函数,得到一个节点对象,进入递归对比更新
      • 类组价:
        1. 重用之前的实例
        2. 调用生命周方法getDerivedStateFromProps
        3. 调用声明周期方法shouldComponentUpdate,若该方法返回false,终止
        4. 运行render,得到新的节点对象,进入递归对比更新
        5. 将该对象的getSnapshotBeforeUpdate加入队列
        6. 将该对象的componentDidUpdate加入队列
    • 数组节点:遍历数组,进行递归对比更新
  • 不一致
    整体上,卸载旧的节点,全新创建新的节点
    先完成挂载新节点,再卸载旧节点

    • 创建新节点
      进入新节点的挂载流程
    • 卸载旧节点
      1. 文本节点、DOM节点、数组节点、空节点、函数组件节点:直接放弃该节点,如果节点有子节点,递归卸载节点
      2. 类组件节点
        1. 直接放弃该节点
        2. 调用该节点的componentWillUnMount函数
        3. 递归卸载子节点
没有找到对比的目标
  • 创建新加入的节点
  • 卸载多余的节点