React 设计原理 Part 2 架构篇

Posted by CodingWithAlice on November 11, 2023

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 一致

render1

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 的本质是二进制数的位运算、状态和优先级的操作大量使用了位运算

简易流程图如下:

render1

render2

1.2 completeWork

completeWork 主要流程:

  • 步骤1⃣️:创建或者标记元素更新( 构建DOM Tree
  • 步骤2⃣️:flags 冒泡(completeWork 结束后,所有被标记的 flags 都在 HostRootFiber.subtreeFlags 中定义)

【注】mount 和 update 中 completeWork 的 区别 在于:

  • mount 是属性的初始化
  • update 是属性 的更新:主函数 diffProperties 包括2次遍历:
    • 第一遍 标记 删除更新后没有的属性
    • 第二遍 标记 更新update流程前后发生改变的属性(保存在 fiberNode.updateQueue 中)

简易流程图如下:

render3

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 对应的操作

commit1

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

commit2

不会被捕获的四种错误:

  • 事件回调中的错误(不属于 react 工作流,且 错误处理 API 被设计的目的是避免错误发生导致 UI 显示不完全 - 只有 commit阶段的Mutation阶段会造成 UI 变化
  • 异步代码(不属于 react 工作流,例如 setTimeout)
  • SSR(不属于 react 工作流)
  • 在 Error Boundaries 所属 Component 内发生的错误