前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • Vue

    • 从源码解读Vue生命周期
    • 组件的本质
    • 有状态组件的设计
    • 设计 VNode
    • 辅助创建 VNode 的 h 函数
    • 自定义渲染器和异步渲染
    • 渲染器之挂载
    • 渲染器的核心 Diff 算法
    • 渲染器之patch
    • 图解 Vue 响应式原理
    • 图解 Vue 异步更新
    • 剖析 Vue 内部运行机制
    • vue响应式原理模拟
    • vue状态管理之vuex
    • 理解Vue的设计思想及实现Vue
    • diff算法深入
    • vue router vuex原理分析
    • Vue3初探响应式原理.
    • vue2源码分析
    • vue组件化实践
  • React

  • Webpack

  • Node

  • Javascript

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

剖析 Fiber 架构下 Concurrent 模式的实现原理|原理篇

文章学习导航
面试总览

30 秒速记

  • 核心判断:Fiber 把不可中断的递归遍历改造成可保存进度的工作单元,使渲染阶段能够调度、暂停和恢复
  • 原理主线:围绕 「current 树 与 workInPr」、「update 的创建」、「Scheduler——“时间切片”与“优」 建立输入、状态变化与输出之间的因果关系
  • 文章范围:解析了 React Fiber 架构下 Concurrent 模式(并发模式)的实现原理,详细讲解了时间切片、优先级调度、current 树与 workInProgress 树的双缓冲机制,以及它们如何提升 React 应用的性能和响应速度
  • 边界与代价:可中断的是渲染计算,提交阶段仍需保持一致性;React 18+ 并发能力也不等于所有更新都并行执行
  • 工程落地:优化时先减少无效工作和长任务,再结合优先级、过渡更新和切片机制改善响应性

Fiber 将递归渲染拆成可暂停、可恢复的工作单元,再由 Scheduler 通过时间切片和优先级安排执行。 workLoopConcurrent 每处理一个单元都会借助 shouldYield() 判断是否该让出主线程,未完成的工作随后可继续。current 树负责展示,workInProgress 树在内存中计算,并通过 alternate 复用节点,提交后两者身份切换。具体函数和优先级实现可能随版本变化,工程中应以实际版本和性能面板为准。

这篇文章不要按 API 清单来背。先用上面的 Mind Map 建立全局结构,再通过交互 DEMO 观察正常路径和边界路径如何改变状态;阅读正文时重点核对每一步的输入、负责执行的参与者、产生的中间状态以及最终可观察结果。遇到版本敏感结论,要把“历史实现”“当前行为”和“工程兼容策略”分开说明;遇到性能或架构取舍,则用实际指标、失败现象和验证手段支撑判断。

版本校准: 原理文章中的代码代表特定实现与写作时间。应用到当前项目时,应先确认浏览器、框架或工具的主版本,再区分稳定的规范语义、可变化的内部实现和项目自身约束。

通过对整个 ReactDOM.render 所触发的渲染链路进行了分析和串联,我们已经把 Fiber 架构在实现层面的大部分要点都过了一遍。刚讲过的这部分知识,一方面相对来说复杂度比较高,需要一些耐心反复地理解和消化;另一方面,本讲接下来要讲解的内容,也和它存在着较强的依赖关系,因此对这些前置知识的把握就显得尤为重要。

下面我说几个函数,帮你检验一下自己的学习效果:

  • performSyncWorkOnRoot
  • workLoopSync
  • performUnitOfWork
  • beginWork
  • completeWork
  • completeUnitOfWork
  • reconcileChildFibers

本讲我将带你去认识 Fiber 架构最迷人的那一面——Concurrent 模式(异步渲染)下的“时间切片”和“优先级”实现。

# current 树 与 workInProgress 树:“双缓冲”模式在 Fiber 架构下的实现

# 什么是“双缓冲”模式

“双缓冲”模式其实是一种在游戏领域由来已久的经典设计模式。为了帮助你快速理解它,这里我先举一个生活中的例子:假如你去看一场总时长只有 1 个小时的话剧,这场话剧中场不休息,需要不间断地演出。

按照剧情的需求,半个小时处需要一次转场。所谓转场,就是说话剧舞台的灯光、布景、氛围等全部要切换到另一种风格里去。在不中断演出的情况下,想要实现转场,怎么办呢?场务工作做得再快,也要十几二十分钟,这对一场时长 1 小时的话剧来说,实在太漫长了。观众也无法接受这样的剧情“卡顿”体验。

有一种解法,那就是准备两个舞台来做这场戏,当第一个舞台处于使用中时,第二个舞台的布局已经完成。这样当第一个舞台的表演结束时,只需要把第一个舞台的灯光灭掉,第二个舞台的灯光亮起,就可以做到剧情的无缝衔接了。

事实上,在真实的话剧中,我们也确实常常看到这样的画面——演员从舞台的左侧走到了右侧,灯光一切换,就从卧室(左侧舞台)走到了公园(右侧舞台);又从公园(右侧舞台)走到了办公室(左侧舞台)。左侧舞台的布景从卧室变成了办公室,这个过程正是在演员利用右侧舞台表演时完成的。

在这个过程中,我们可以认为,左侧舞台和右侧舞台分别是两套缓冲数据,而呈现在观众眼前的连贯画面,就是不同的缓冲数据交替被读取后的结果。

在计算机图形领域,通过让图形硬件交替读取两套缓冲数据,可以实现画面的无缝切换,减少视觉效果上的抖动甚至卡顿。而在 React 中,双缓冲模式的主要利好,则是能够帮我们较大限度地实现 Fiber 节点的复用,从而减少性能方面的开销。

# current 树与 workInProgress 树之间是如何“相互利用”的

在 React 中,current 树与 workInProgress 树,两棵树可以对标“双缓冲”模式下的两套缓冲数据:当 current 树呈现在用户眼前时,所有的更新都会由 workInProgress 树来承接。workInProgress 树将会在用户看不到的地方(内存里)悄悄地完成所有改变,直到“灯光”打到它身上,也就是 current 指针指向它的时候,此时就意味着 commit 阶段已经执行完毕,workInProgress 树变成了那棵呈现在界面上的 current 树。

接下来我将用一个 Demo,带你切身感受一把 workInProgress 树和 current 树“相互利用”的过程。代码如下:

import { useState } from 'react';
function App() {
  const [state, setState] = useState(0)
  return (
    <div className="App">
      <div onClick={() => { setState(state + 1) }} className="container">
        <p style={{ width: 128, textAlign: 'center' }}>
          {state}
        </p>
      </div>
    </div>
  );
}

export default App;
@前端进阶之旅: 代码已经复制到剪贴板

这个组件挂载后呈现出的界面很简单,就是一个数字 0,如下图所示:

# 挂载后的 Fiber  树

关于 Fiber 树的构建过程,前面已经详细讲解过,这里不再重复。下面我直接为你展示挂载时的 render 阶段结束后,commit 执行前,两棵 Fiber 树的形态,如下图所示:

待 commit 阶段完成后,右侧的 workInProgress 树对应的 DOM 树就被真正渲染到了页面上,此时 current 指针会指向 workInProgress 树:

由于挂载是一个从无到有的过程,在这个过程中我们是在不断地创建新节点,因此还谈不上什么“节点复用”。节点复用要到更新过程中去看。

第一次更新

现在我点击数字 0,触发一次更新。这次更新中,下图高亮的 rootFiber 节点就会被复用:

这段复用的逻辑在 beginWork 调用链路中的 createWorkInProgress 方法里。这里我为你截取了 createWorkInProgress 方法里面一段非常关键的逻辑,请看下图:

在 createWorkInProgress 方法中,会先取当前节点的 alternate 属性,将其记为 workInProgress 节点。对于 rootFiber 节点来说,它的 alternate 属性,其实就是上一棵 current 树的 rootFiber,如下图高亮部分所示:

当检查到上一棵 current 树的 rootFiber 存在时,React 会直接复用这个节点,让它作为下一棵 workInProgress 的节点存在下去,也就是说会走进 createWorkInProgress 的 else 逻辑里去。如果它和目标的 workInProgress 节点之间存在差异,直接在该节点上修改属性、使其与目标节点一致即可,而不必再创建新的 Fiber 节点。

至于剩下的 App、div、p 等节点,由于没有对应的 alternate 节点存在,因此它们的 createWorkInProgress 调用会走进下图高亮处的逻辑中:

在这段逻辑里,将调用 createFiber 来新建一个 FiberNode。

第一次更新结束后,我们会得到一棵新的 workInProgress Fiber 树,current 指针最后将会指向这棵新的 workInProgress Fiber 树,如下图所示:

第二次更新

接下来我们再次点击数字 1,触发 state 的第二次更新。

在这次更新中,current 树中的每一个 alternate 属性都不为空(如上图所示)。因此每次通过 beginWork 触发 createWorkInProgress 调用时,都会一致地走入 else 里面的逻辑,也就是直接复用现成的节点。

以上便是 current 树和 work 树相互“打配合”,实现节点复用的过程。

# 更新链路要素拆解

在上一讲,我们已经学习了挂载阶段的渲染链路。同步模式下的更新链路与挂载链路的 render 阶段基本是一致的,都是通过 performSyncWorkOnRoot 来触发包括 beginWork、completeWork 在内的深度优先搜索过程。这里我为你展示一个更新过程的调用栈,请看下图:

你会发现还是熟悉的配方,还是原来的味道。其实,挂载可以理解为一种特殊的更新,ReactDOM.render 和 setState 一样,也是一种触发更新的姿势。在 React 中,ReactDOM.render、setState、useState 等方法都是可以触发更新的,这些方法发起的调用链路很相似,是因为它们最后“殊途同归”,都会通过创建 update 对象来进入同一套更新工作流。

# update 的创建

接下来我继续以开篇的 Demo 为例,为你拆解更新链路中的要素。在点击数字后,点击相关的回调被执行,它首先触发的是 dispatchAction 这个方法,如下图所示:

请你关注图中两处标红的函数调用,你会看到 dispatchAction 方法在 performSyncWorkOnRoot 的左边。也就是说整体的更新链路应该是这样的:

dispatchAction 中,会完成 update 对象的创建,如下图标红处所示:

# 从 update 对象到 scheduleUpdateOnFiber

等等,这段逻辑你是否觉得似曾相识?如果你对 ReactDOM.render 系列的第一课时还有印象的话,我希望你能回忆起 updateContainer 这个方法。在 updateContainer 中,React 曾经有过性质一模一样的行为,这里我为你截取了 updateContainer 函数中的相关逻辑:

图中这一段代码的逻辑是非常清晰的,以 enqueueUpdate 为界,它一共做了以下三件事。

  • enqueueUpdate 之前:创建 update。
  • enqueueUpdate 调用:将 update 入队。这里简单说下,每一个 Fiber 节点都会有一个属于它自己的 updateQueue,用于存储多个更新,这个 updateQueue 是以链表的形式存在的。在 render 阶段,updateQueue 的内容会成为 render 阶段计算 Fiber 节点的新 state 的依据。
  • scheduleUpdateOnFiber:调度 update。如果你对之前学过的知识还有印象,会记得同步挂载链路中,这个方法后面紧跟的就是 performSyncWorkOnRoot 所触发的 render 阶段,如下图所示:

← ReactDOM.render 是如何串联渲染链路的React 事件与 DOM 事件有何不同 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • Vue

    • 从源码解读Vue生命周期
    • 组件的本质
    • 有状态组件的设计
    • 设计 VNode
    • 辅助创建 VNode 的 h 函数
    • 自定义渲染器和异步渲染
    • 渲染器之挂载
    • 渲染器的核心 Diff 算法
    • 渲染器之patch
    • 图解 Vue 响应式原理
    • 图解 Vue 异步更新
    • 剖析 Vue 内部运行机制
    • vue响应式原理模拟
    • vue状态管理之vuex
    • 理解Vue的设计思想及实现Vue
    • diff算法深入
    • vue router vuex原理分析
    • Vue3初探响应式原理.
    • vue2源码分析
    • vue组件化实践
  • React

  • Webpack

  • Node

  • Javascript

  • 综合