React的一次完整渲染流程

736 阅读3分钟

一次完整的渲染流程

  1. JSX代码通过React.createElement方法生成ReactElement结构
    • ReactElement结构是树结构,由根节点向子节点扩展
    • 节点之间根据child连接
  2. Element结构通过schedule的调度reconcile去生成fiber结构树
    • fiber结构是链表结构,由fiber根节点(HostRootFiber)开始延展
    • 节点之间根据child、return、sibling的关系组成链表
    • React18正式推出了并发更新,是基于fiber的链表结构实现
    • 🚧注意:虽然ReactV16就提出了fiber,但是并没有默认使用并发特性
  3. fiber结构树通过commit操作生成真实DOM元素
    • commit分为三个阶段before mutation、mutation、layout
    • before mutation在DOM操作之前
    • mutation阶段在增删DOM节点
    • layout阶段在DOM操作之后

render阶段

举个🌰

export default function App() {
  const [count,setCount] = useState(0)
  
  const handleClick=()=>{
    setCount(count+1)
  }

  return(
    <div>
      hello akechi,
      <p> 这是我的count,值为{count} </p>
      <button onClick={handleClick} >按钮</button>
    </div>
  )
}

🚧前置概念:

  • 双缓冲树:React的fiber树有两颗
    • 在渲染层展示,根节点fiberRoot的current指向它,所以它也称为current
    • 在内存中构建的fiber树是workInProgressFiber
    • 当构建的workInProgressFiber完成后,React通过current指向将内存中的fiber树挂载,此时这棵树变成了current。当React需要更新时,会根据current复用没有改变的fiber节点,从而加速fiber树的构造速度

在构建fiber树的过程中:

  1. 初始化
    • 创建一个fiberRoot作为整个应用的根节点,需要注意的是整个应用只有一个fiberRoot
    • 创建HostRootFiberfiberRoot的current指向HostRootFiber,一个应用可以有多个HostRootFiber
    • 复用当前current树的alternate作为workInProgressFiber,如果没有alternate就创建
  2. workLoop循环阶段:
    • fiber树的构造是深度优先遍历
    • wip!==null时,循环调用performUnitOfWork方法
    • 通过beginWork方法向下调和找到next节点,当next无sibling节点(兄弟节点)调用,completeWork方法向上归并
    • workInProgress指针逐个构造fiber节点
  3. 构造完成
    • workInProgress置为nul
    • workInProgressFiber构建完成,fiberRoot的current指向它

在更新fiber树节点过程中:

  1. 以渲染层的fiber树为基础,复用fiber节点,并修改更新的fiber节点
  2. fiberRoot更改current指向,切换workinProgressFiber为渲染层的fiber树

render的中断渲染在哪里?

首先看下调度中心示意图

React根据scheduler包实现了任务调度,然后workLoop中使用停顿机制,具体可以参考这篇文章React的调度原理

function workLoopConcurrent() {
  //shouldYield开启实现停顿
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

React的中断渲染的原理:当监测到执行任务队列中的task超时,shouldYield为true,退出循环把线程交还给浏览器进行更重要的工作(如:绘制UI防止界面卡顿),等浏览器空闲时调用requestHostCallback发送MessgaeChannel重新循环处理task队列

commit阶段的三个阶段

  1. before muation:处于执行DOM操作之前,比如获取DOM节点的快照,异步调取useEffect
  2. Muation :处于DOM渲染的过程,执行真正的DOM操作
  3. layout:处于DOM更新完成后的过程,比如执行setState的回调,或者触发useLayoutEffect等钩子

🚧🚧提问:为什么React要设计useEffect、useLayoutEffect两个钩子呢?

如果你在useEffct的初始化渲染中修改了展示的数据或者css样式,那么很有可能会重复渲染导致闪屏用户体验不好的问题,此时可以试下useLayoutEffect这个钩子