React 设计原理 Part 2 架构篇
本文主要是对《React 设计原理》一书的笔记和总结,方便之后回顾只看笔记,不再重新细读书籍。
目录
render 阶段
1.1、beginWork
1.2、completeWork
commit 阶段
2.1、将各种副作用 commit 到宿主环境 UI 中
2.2、错误处理
还是老样子,阅读这一章之前,明确问题:
1、React 的渲染怎么实现的?
解答:主要分为两个阶段,分别为 render阶段和commit 阶段。
render 阶段【异步执行】 - WIP FiberTree:
- 先从 HostRootFiber 向下 DFS 遍历,执行 beiginWork
- beginWork :根据 wip.tag 进入不同类型元素处理,生成下一级的 fiberNode - 即子节点,并标记副作用 flags(删除 ChildDeletion/新增、移动Placement)
- 再遍历到叶子节点时,调用 completeWork 处理 fiberNode
- completeWork:创建或标记元素更新,构建出 tree;同时 flags 冒泡/收集 EffectList 单向链表
commit 阶段【同步执行】 - 将各种副作用 commit 到宿主环境 UI 中:
-
当 WIP FiberTree 生成完毕,FiberRootNode会被传递给 Render渲染器 -commit阶段,执行 side-effects-list链表【在18改为 flags/subtreeFlags】
-
Renerder-commit 完成工作后,WIP FiberTree 已经渲染到宿主环境中,FiberRootNode.current 切换指向 WIP
2、React 如何处理异常?
解答:错误边界(两个API),详见 2.3
render 阶段
React 18 中的并发功能,在 render 中使用 shouldYield判断是否可中断,其他逻辑和17的 render 一致
render 阶段的主要流程
- 先递:从 HostRootFiber 开始向下以 DFS 遍历,执行 beginWork
- 再归:遍历到叶子节点时,调用 completeWork 处理 fiberNode
1.1 beginWork
beginWork 主要流程:
- 步骤1⃣️:根据 wip.tag 进入不同类型元素的处理分支
- 步骤2⃣️:使用 reconcile 算法生成 下一级 fiberNode
【注】mount 和 update 中 beginWork 的 区别 在于:
- 1、是否能够复用 Current fiberNode
- 2、最终是否为生成的 子fiberNode 标记副作用 flags(例如 Placement-新增、移动,ChildDeletion 删除…)
补充:React 中的位运算 - 标记 flags 的本质是二进制数的位运算、状态和优先级的操作大量使用了位运算
简易流程图如下:
1.2 completeWork
completeWork 主要流程:
- 步骤1⃣️:创建或者标记元素更新( 构建DOM Tree)
- 步骤2⃣️:flags 冒泡(completeWork 结束后,所有被标记的 flags 都在 HostRootFiber.subtreeFlags 中定义)
【注】mount 和 update 中 completeWork 的 区别 在于:
- mount 是属性的初始化
- update 是属性 的更新:主函数 diffProperties 包括2次遍历:
- 第一遍 标记 删除更新后没有的属性
- 第二遍 标记 更新update流程前后发生改变的属性(保存在 fiberNode.updateQueue 中)
简易流程图如下:
commit 阶段
commit 阶段(Renderer工作的阶段 - 同步执行直至完成)
主要功能:将各种副作用 commit 到宿主环境 UI 中
2.1 commit 流程拆解
- BeforeMutation阶段
- Mutation阶段:执行 DOM 操作
- 删除的数组 deletions 是在 render阶段遍历生成的
- 删除操作以 DFS 顺序遍历子树的每个 fiberNode 执行操作
- updateQueue 中存储的是“变化属性的 key-value”,在此阶段被执行(包括 style 属性、innerHTML、文本节点、其他元素属性)
- — Fiber Tree 切换 —
- Layout阶段(看图)
每一个阶段都以 DFS遍历,直到叶子节点或者无 flags 的节点,再向上遍历到 HostRootFiber,在遍历构成中执行 flags 对应的操作
2.2 Effect List 和 flags
在 React18 中,为了在开启并发更新后,使 render阶段可被打断,且实现多个 render 对应一个 commit 阶段,重构实现了 subtreeFlags
- 遍历链表比遍历树高效得多
- 未开启并发更新时,一次 render 阶段对应一次 commit 阶段
2.3 错误处理
React 提供了两个错误处理相关的 API
- getDerivedStateFromError:静态方法,有错误时,提供时机渲染 callback UI
- componentDidCatch:组件实例方法,有错误时,提供时机记录错误信息
使用这两个 API 的组件被称为 Error Boundaries(错误边界)- 捕获 React 工作流(render+commit)中的错误
处理错误流程,整体分为三个阶段:
- 捕获错误
- 构造 callback
- 执行 callback
不会被捕获的四种错误:
- 事件回调中的错误(不属于 react 工作流,且 错误处理 API 被设计的目的是避免错误发生导致 UI 显示不完全 - 只有 commit阶段的Mutation阶段会造成 UI 变化)
- 异步代码(不属于 react 工作流,例如 setTimeout)
- SSR(不属于 react 工作流)
- 在 Error Boundaries 所属 Component 内发生的错误