核心内容摘要
糖心少女的镜头语言:穿越时空的文学风雅与少女心事
React 渲染架构底层渲染流程大致可以分为两大阶段render 阶段Scheduler - Reconciler、commit 阶段Renderer注意这里的 render 阶段和 Renderer 渲染器是两个东西主要的阶段可以
总结为两个公式const state reconcile(update); // 通过 reconciler 计算出最新的状态 const UI commit(state); // 通过最新的 state 渲染出 UI
调度器Scheduler渲染的入口与优先级控制Scheduler 类似于浏览器的原生 API requestIdleCallback。
React 团队没有使用 requestIdleCallback 是因为考虑为兼容性问题。
Scheduler 是整个渲染执行链条的入口负责将变更转化为任务按优先级调度任务执行支持时间切片与可中断渲染虽然每一帧绘制时间约为
1
66ms但是如果屏幕没有刷新那么浏览器会安排长度为 50ms 左右的空闲时间。
研究表明用户操作之后100ms 以内的响应给用户的感觉都是瞬间发生也就是说不会感受到延迟感因此将空闲时间设置为 50浏览器依然还剩下 50ms 可以处理用户的操作响应不会让用户感到延迟这就是我们面试题中常看到的浏览器渲染每帧的空闲时间。
更新任务被排队当你调用setState、dispatch、startTransition、事件触发更新时React 内部会scheduleUpdateOnFiber(fiber, lane, eventTime)这个函数是调度入口它将更新附着在对应的 Fiber 上并根据任务的lane内部优先级更新任务队列。
任务优先级系统LanesReact 内部并不使用单一队列而是用多 lane 机制。
优先级由 Scheduler 根据事件来源与延迟expiration time决定。
时间切片Time SlicingScheduler 会根据浏览器的空闲时间决定是在当前帧继续执行还是 yield 给浏览器这样协调器起点也是不同的performSyncWorkOnRoot同步更新流程performConcurrentWorkOnRoot并发更新流程// performSyncWorkOnRoot 会执行该方法 function workLoopSync(){ while(workInProgress ! null){ performUnitOfWork(workInProgress) } } // performConcurrentWorkOnRoot 会执行该方法 function workLoopConcurrent() { // shouldYield 表明任务是否可以中断 // 如果 workInProgress 为 null说明已经没有下一个 FiberNode也就是说明整颗 Fiber tree 树构建完毕 while (workInProgress ! null !shouldYield()) { // performUnitOfWork创建下一个 FiberNode并且将已创建的 FiberNode 连接起来child、return、sibling形成一个链表结构的 Fiber tree workInProgress performUnitOfWork(workInProgress); } }shouldYield判断当前帧是否该让出避免阻塞主线程。
中断 恢复机制如果有更高优先级的任务中断当前 render后续在合适时机 恢复执行。
这样能确保交互响应不会被大更新阻塞。
协调器ReconcilerFiber 树的 diff 与标记协调器的核心任务是对比新旧树确定哪些节点变化生成workInProgress Fiber 树标记变化Placement、Update、Deletion不直接修改 DOM而是将变化收集起来Fiber 数据结构每一个组件在运行时都有一个 Fiber 节点指向真实 DOM 实例链接 child / sibling / return 三个方向记录当前组件状态、props、更新队列、优先级等属性这种结构不同于简单递归栈它更像一颗可分片执行的链表树。
Reconciler 工作循环 —— render 阶段协调器是由 Scheduler 调度后执行的。
React 会从 Root Fiber 开始 traverse。
a. beginWork处理当前节点逻辑根据类型函数组件 / 类组件 / Fragment 等判断需要构建哪些子 Fiber。
b. completeWork在子节点构建完成后整合变化把节点的更改标记写入flags。
// 深度优先遍历 function performUnitOfWork(fiberNode){ // 省略 beginWork if(fiberNode.child){ performUnitOfWork(fiberNode.child); } // 省略 CompleteWork if(fiberNode.sibling){ performUnitOfWork(fiberNode.sibling); } }整个过程构成一个 双缓冲机制current Fiber当前渲染状态workInProgress Fiber正在构建的树最终workInProgress树会用于提交阶段。
标记变化协调器不会直接操作 DOM而是根据差异为每个节点打上 flagsPlacement新节点插入Update属性/文本内容更新Deletion删除节点这些标记会在提交阶段被 Renderer 读取。
渲染器Renderer真实环境应用变化Renderer 是把协调阶段生成的变化同步应用到最终环境比如浏览器的 DOMReact Native 原生视图其他自定义渲染平台React 通过隔离平台实现了 Renderer 的可插拔。
相较于 render 阶段此时 commit 阶段不可中断恢复。
提交阶段的三个子阶段协调阶段完成后进入 commit 阶段a. Before Mutation执行getSnapshotBeforeUpdate类组件记录焦点、selection输入框光标等。
commitBeforeMutationEffects(root, finishedWork) // 读取旧的 DOM 状态b. Mutation真实 DOM 操作发生位置根据flags做appendChild、removeChild、update 属性、插入节点等。
然后切换 Fiber Tree也就是上面提到的双缓冲机制两棵树互换。
c. LayoutuseLayoutEffect 的意义是在 DOM 更新完成后浏览器绘制之前同步读取或修改布局。
执行useLayoutEffect、componentDidMount、componentDidUpdate的回调。
注意哦这里虽然 DOM 已经变更了但是浏览器页面还没有 paint下一步才是绘制。
渲染器应用 DOM 改变commitUpdate(domElement, updatePayload, type, oldProps, newProps) // 调用一系列 DOM API 为元素属性更新赋值这里 paint 之后才会执行 useEffect异步的回调。
所以如果需要操作 DOM 要在 useLayoutEffect 钩子执行。