Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 编码专题

  • 深入浅出 Vite

  • 快速上手 API

  • 深入浅出Babel

  • 深入浅出 React

    • React源码-常量定义
    • React源码-WorkInProgress常用操作
    • React源码-目录结构
    • React源码-workInProgress详解
    • React源码-ReactFiberWorkLoop.js
    • React源码-completeWork
    • React源码-commit前后阶段工作
      • 0.前言
      • 1.结构及作用
      • 2. 入口逻辑回顾
      • 3. commitRoot 代码及本节核心工作说明
      • 4. commitRootImpl 准备阶段工作
        • 4.1 处理 useEffect 回调
        • 4.2 处理全局变量
        • 4.3 处理 completeUnitOfWork 中留下的一个坑
        • 4.4 处理离散事件
      • 5. commitRootImpl 结尾工作
        • 5.1 判断当前节点是否会触发 useEffect
        • 5.2 检测是否无限循环同步任务
        • 5.3 继续调度
        • 5.4 useLayoutEffect 回调执行流程
      • 6. 完整 commitRootImpl 工作流程梳理
      • 待解释内容:
      • 补充内容:
    • React源码-commit核心
  • 百问掘金
  • 深入浅出 React
wangjiasheng
2023-07-11
目录

React源码-commit前后阶段工作

# 0.前言

此部分内容主要涉及的 commit 中的大体逻辑,主要是讲解的起承转合的作用。

# 1.结构及作用

主要还是在 ReactFiberWorkLoop.old.js 这个文件夹下。

核心功能:处理 EffectList 链表。

# 2. 入口逻辑回顾

回顾下,当 render 结束后,会执行如下代码:

虽然下面举例的同步代码,performConcurrentWorkOnRoot 逻辑稍有不同,基础逻辑类似:

// 调度: render 阶段 => commit 阶段
function performSyncWorkOnRoot(root) {
  // 省略若干代码.....
  var finishedWork = root.current.alternate;
  root.finishedWork = finishedWork;
  // commit 阶段入口
  commitRoot(root);
  // 最后会对 RootFiber 再一次进行调度
  ensureRootIsScheduled(root, now());
}
1
2
3
4
5
6
7
8
9
10

并且 root 根节点上会保存 finishedWork 属性值,保存当前已完成计算的树。

对于结构:

const root = ReactDOM.unstable_createRoot(document.getElementById("root"));
root.render(<App />);

function App() {
  const [num, setNum] = useState(1);
  return (
    <div className="App">
      <header className="App-header">
        <p onClick={() => setNum((x) => x + 1)}>
          <code title={num}>{num}</code>
        </p>
      </header>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

此时:

  • finishedWork
    • tag 为 3,即 HostRoot 组件。
  • finishedWork.child
    • tag 为 0 ,即 FunctionComponent 组件,对应于 function APP(){}
  • finishedWork.child.child
    • tag 为 5,即 HostComponent 组件,type 为 <div />
  • finishedWork.firstEffect
    • tag 为 5,即 HostComponent 组件,type 为 code

# 3. commitRoot 代码及本节核心工作说明

在此阶段引入一个核心函数 runWithPriority ,以第一个参数 ImmediateSchedulerPriority 立即调用优先级,执行 commtiRootImpl 函数。

function commitRoot(root) {
  runWithPriority(ImmediateSchedulerPriority, commitRootImpl.bind(null, root));
  return null;
}
1
2
3
4

而所有的核心的逻辑,全封装在 commitRootImpl,整体逻辑如下:

function commitRootImpl(root, renderPriorityLevel) {
  // 0.准备工作
  // do something before beforeMutaion

  const finishedWork = root.finishedWork;
  firstEffect = finishedWork.firstEffect;

  // 若存在副作用节点,则执行三个循环语句
  if (firstEffect !== null) {
    nextEffect = firstEffect;
    do {
      // 1.before mutation
    } while (nextEffect !== null);

    do {
      // 2.mutation
    } while (nextEffect !== null);

    do {
      // 3.layout
    } while (nextEffect !== null);
  }
  {
    // No effects.
    root.current = finishedWork;
  }

  // 4.结尾工作
  // do something after layout
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

整体逻辑还是很清晰的,commit 阶段就是处理 EffectList 链表的,那首先第一件事就是需要存不存在需要更新的副作用节点。

如果需要更新,则会执行三个 do...while 循环,在此阶段中执行的核心代码(beforeMutation、mutation、layout )。

至此,可总结出此阶段需要做的工作主要有五块:

  1. 【本节阐述】准备工作
  2. before mutation 阶段(执行 DOM 操作之前)
  3. mutation 阶段(执行 DOM 操作)
  4. layout 阶段(执行 DOM 操作后)
  5. 【本节阐述】结尾工作

为了更好的叙述 commit 流程,其中 [1][2][3] 阶段的工作会单独拆分章节分析, 本节重点关注 [0][4] 流程。

# 4. commitRootImpl 准备阶段工作

# 4.1 处理 useEffect 回调

do {
  // useEffect 的回调会被保存在这个 flushPassiveEffects 中
  flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
1
2
3
4

# 4.2 处理全局变量

在前面的 beginWork 以及 completeWork 阶段的分析可以知道,存在很多的全局变量用于控制函数的一个执行状态的。

比如说最重要的是 workInProgress 全局变量,其中存放着的是指向 FiberNode 的一个指针。

因此在 commit 阶段的入口,需要对这些指针进行重置操作。

if (root === workInProgressRoot) {
  // We can reset these now that they are finished.
  workInProgressRoot = null;
  workInProgress = null;
  workInProgressRootRenderLanes = NoLanes;
} else {
  // This indicates that the last root we worked on is not the same one that
  // we're committing now. This most commonly happens when a suspended root
  // times out.
}
1
2
3
4
5
6
7
8
9
10

在 reconciler 协调器入口会判断 workInProgress 是否为 null 判断是否进入,进入 commit 阶段后,保险起见重置。

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}
1
2
3
4
5

# 4.3 处理 completeUnitOfWork 中留下的一个坑

观察 EffectList 链表产生的时候发现一个问题:EffectList 缺少对 root 节点的处理。

const returnFiber = completedWork.return;
if (returnFiber !== null && (returnFiber.flags & Incomplete) === NoFlags) {
}
1
2
3

当 workInProgress 指向 ReactDOM.render 时,是无法修改父组件的

// 结果:如果 root 存在 EffectTag,则 firstEffect 为自己;如果不存在,则直接就用第一个 firstEffect 即可。
let firstEffect;
if (finishedWork.flags > PerformedWork) {
  if (finishedWork.lastEffect !== null) {
    finishedWork.lastEffect.nextEffect = finishedWork;
    firstEffect = finishedWork.firstEffect;
  } else {
    firstEffect = finishedWork;
  }
} else {
  firstEffect = finishedWork.firstEffect;
}
1
2
3
4
5
6
7
8
9
10
11
12

但是这种常见比较少,暂时不清楚什么情况会去修改?难道是处理 render 函数的第三个回调吗?

ReactDOM.render(<App />, document.getElementById("app"), () => {
  console.log("render complete");
});
1
2
3

# 4.4 处理离散事件

代码如下:

if (rootsWithPendingDiscreteUpdates !== null) {
  if (
    !hasDiscreteLanes(remainingLanes) &&
    rootsWithPendingDiscreteUpdates.has(root)
  ) {
    rootsWithPendingDiscreteUpdates.delete(root);
  }
}
1
2
3
4
5
6
7
8

这里出现了一个词 DiscreteUpdates 离散更新,那什么行为会触发该操作呢?常见的更新如用户的点击行为。此时 react 需要处理 focus 等事件。

# 5. commitRootImpl 结尾工作

# 5.1 判断当前节点是否会触发 useEffect

暂时跳过,后期补充,暂时不理解。

const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;

// useEffect 相关逻辑
if (rootDoesHavePassiveEffects) {
  // This commit has passive effects. Stash a reference to them. But don't
  // schedule a callback until after flushing layout work.
  rootDoesHavePassiveEffects = false;
  rootWithPendingPassiveEffects = root;
  pendingPassiveEffectsLanes = lanes;
  pendingPassiveEffectsRenderPriority = renderPriorityLevel;
} else {
  // We are done with the effect chain at this point so let's clear the
  // nextEffect pointers to assist with GC. If we have passive effects, we'll
  // clear this in flushPassiveEffects.
  nextEffect = firstEffect;
  while (nextEffect !== null) {
    const nextNextEffect = nextEffect.nextEffect;
    nextEffect.nextEffect = null;
    if (nextEffect.flags & Deletion) {
      detachFiberAfterEffects(nextEffect);
    }
    nextEffect = nextNextEffect;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 5.2 检测是否无限循环同步任务

此时会对同步更新进行计数,判断是否为无线更新

// 取出根节点的 Lane 优先级
remainingLanes = root.pendingLanes;
if (remainingLanes === SyncLane) {
  // Count the number of times the root synchronously re-renders without
  // finishing. If there are too many, it indicates an infinite update loop.
  if (root === rootWithNestedUpdates) {
    nestedUpdateCount++;
  } else {
    nestedUpdateCount = 0;
    rootWithNestedUpdates = root;
  }
} else {
  nestedUpdateCount = 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

@todo:这里需要案例才可以更直观的了解,要不然还是一知半解。

# 5.3 继续调度

这段代码出现很多次了,但是还是无法了解的很深入

ensureRootIsScheduled(root, now());
1

# 5.4 useLayoutEffect 回调执行流程

源码部分:

flushSyncCallbackQueue();
1

这部分可以结合案例说明:

function App() {
  const [num, setNum] = useState(1);
  useLayoutEffect(() => {
    if (num === 2) {
      setNum(num + "Layout!");
    }
  }, [num]);
  return (
    <div className="App">
      <header className="App-header">
        <p onClick={() => setNum((x) => x + 1)}>
          <code title={num}>{num}</code>
        </p>
      </header>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

点击 1 → 2 时,打断点如下:

// debugger;
flushSyncCallbackQueue(); // 执行 useLayout Effect 回调
1
2

可以发现在断点处,页面显示状态为 2,此时开始执行 flushSyncCallbackQueue :

  • 执行后,页面会进入 performSynWorkOnRoot 同步执行逻辑。

  • 在 ``performSynWorkOnRoot函数中此时会再次执行render和commit阶段,对应进入的函数名为renderRootSync和commitRoot(root)`。

  • 无乱是 concurrent 和 legacy 进入的都是 commitRoot 这个函数,并不会在函数名上做区分,但在 commitRoot 此时调度会使用 runWithPriority 来调度更新逻辑,由于 performSyncWorkOnRoot 会进入同步调度逻辑。

  • 在同步阶段的 mutation 阶段,即第二个 while 循环结束后,会触发页面视图更新,将 2 变更为 2Layout!

# 6. 完整 commitRootImpl 工作流程梳理

伪代码如下:

function commitRootImpl(root, renderPriorityLevel) {
  do {
    // 执行 useEffect 回调函数,由于 useEffect 会一直存在回调。
    flushPassiveEffects();
  } while (rootWithPendingPassiveEffects !== null);

  const finishedWork = root.finishedWork;

  // 清空一些全局变量
  root.finishedWork = null;
  if (root === workInProgressRoot) {
    // We can reset these now that they are finished.
    workInProgressRoot = null;
    workInProgress = null;
    workInProgressRootRenderLanes = NoLanes;
  }

  // 处理离散事件(如鼠标的点击等,跳过)
  if (rootsWithPendingDiscreteUpdates !== null) {...}

  // 处理 FiberRoot 的 firstEffect 链表
  firstEffect = finishedWork.firsetEffect;

  // 若存在副作用节点,则执行三个循环语句
  if(firstEffect !== null){
    nextEffect = firstEffect;
    do {
      // 1.before mutation
    } while (nextEffect !== null);

    do {
      // 2.mutation 【视图更新】
    } while (nextEffect !== null);

    do {
      // 3.layout
    } while (nextEffect !== null);
  }{
    // No effects.
    root.current = finishedWork;
  }

  // 对 useEffect 的处理

  // 判断是是否为无限循环更新

  // 继续调度
  ensureRootIsScheduled(root, now());

  // 触发 useLayoutEffect → performSyncWorkOnRoot
  flushSyncCallbackQueue()

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 待解释内容:

  1. 如何让某段函数以一个优先级执行?解析 runWithPriority 函数。
  1. rootWithPendingPassiveEffects 这函数是怎么改变的?
  2. flushPassiveEffects 是怎么产生的?

补充机制:在 React 中是如何对性能进行监控的?

# 补充内容:

在 SchedulerWithReactIntegration 文件中,约定了优先级的等级:

export const ImmediatePriority: ReactPriorityLevel = 99;
export const UserBlockingPriority: ReactPriorityLevel = 98;
export const NormalPriority: ReactPriorityLevel = 97;
export const LowPriority: ReactPriorityLevel = 96;
export const IdlePriority: ReactPriorityLevel = 95;
// NoPriority is the absence of priority. Also React-only.
export const NoPriority: ReactPriorityLevel = 90;
1
2
3
4
5
6
7
编辑 (opens new window)
上次更新: 2023/09/17, 22:09:00
React源码-completeWork
React源码-commit核心

← React源码-completeWork React源码-commit核心→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式