一、一些问题
① React 会先构建完整 Element Tree 吗?
不会。但刚开始会执行 <App /> 构建最外层的 Element ,然后接下来的 child 组件是在beginWork中逐层执行,Element 是逐层产生的。
② 两个 alternate 是一个对象吗?
不是。是两个 Fiber 实例。
③ 旧 workInProgress 会销毁吗?
不会。对象会复用,但数据会重置。根据element node 和 current fiber 比如,对旧 workInProgress 进行属性覆盖
④ completeWork 会创建 DOM 吗?
会。但不会把 DOM 插入页面。mount 时会创建离屏 dom 节点树,然后 commit 时直接将该树挂载。updata 时仅进行局部更新
Render 阶段 ├── current Fiber 与新 Element 协调 ├── 构建 workInProgress Fiber 树 ├── 新节点:创建 DOM 实例 ├── 已有节点:复用原 DOM 实例 └── 标记 Placement、Update、Deletion 等操作 Commit 阶段 ├── 删除不再需要的 DOM ├── 插入或移动新增 DOM ├── 更新已有 DOM 的属性和文本 └── 将 finishedWork Fiber 树切换为 currentUpdate 时,completeWork对已经匹配并复用的 Host Fiber,通常不会重新创建 DOM;它直接复用stateNode中原来的 DOM。只有本轮新挂载的 Host Fiber 才会创建新 DOM
⑤ Commit 才创建 DOM 吗?
不是。Commit 负责的是将 Render 准备好的变更应用到真实 DOM 树,包括插入、删除、移动和更新。
⑥ swap 是什么?
commit 的一个阶段
root.current = finishedWork;把新的 Fiber 树变成当前树,而不是交换 DOM。
before mutation ↓ mutation:插入、删除、移动、更新真实 DOM ↓ root.current = finishedWork (swap) ↓ layout:执行 useLayoutEffect、componentDidMount / componentDidUpdate ↓ passive:稍后执行 useEffect⑦ 为什么要先改 DOM,再切换 current ?
因为useLayoutEffect和生命周期需要同时看到:
已经更新后的真实 DOM;
已经切换后的新 Fiber 状态。
8、fiber 内的 stateNode (dom)是否已经有指向关系?
有,假如(update)内部有新增子树,那么会先 appendChild 等先将子树拼接,然后 commit 的时候再找到其父 fiber 的stateNode 进行 appendChild 。已有的 dom 本身也已经有这层关系,但不能在 render 就将拼接的子树接上父 fiber 的 stateNode ,因为这会立即影响到页面。
到了 mutation 阶段,React 处理divFiber的Placement:
从 divFiber.return 向上寻找 ↓ 找到 section Fiber ↓ parent = sectionFiber.stateNode ↓ 取得 divFiber.stateNode ↓ parent.appendChild(divDOM)新增 Fiber 会在 Render 阶段先通过return接入 WIP Fiber 树;新增 Host Fiber 的stateNode保存新 DOM,但不会提前指向或挂入已有父 DOM。若新增节点内部还有新 Host 子树,内部可以在completeWork中先组装;它与已有页面 DOM 的连接,则在 Commit 的 Placement 中通过局部appendChild或insertBefore完成。
completeWork直接通过 DOM API 建立新 DOM 节点之间的真实父子关系,树的顶层 DOM 由对应 Host Fiber 的stateNode引用,后代同时可以通过 DOM 的childNodes和各自 Fiber 的stateNode找到。只有它与已挂载页面 DOM 的最后连接,会推迟到 Commit 阶段。
Mount
所有 Host Fiber 都是新的 ↓ completeWork 从下向上创建 DOM ↓ 新 Host 父节点吸收新 Host 子节点 ↓ 形成离屏 DOM 子树 ↓ Commit 把顶层 DOM 插入根容器Update:只有属性变化
复用 Fiber alternate ↓ 复用 stateNode 中的旧 DOM ↓ 不重新组装 ↓ Commit 局部更新属性或文本Update:新增一棵 Host 子树
新增子树内部: completeWork 中提前组装 新增子树与已有页面父 DOM: Commit 中 appendChild / insertBefore二、流程
1、总的流程
┌──────────────────────────────────────────────────────────┐ │ Trigger / Update │ └──────────────────────────────────────────────────────────┘ root.render(rootElement) setState / dispatch props、context 或 Suspense 变化 │ ▼ 创建并入队 Update │ ▼ 选择更新 Lane │ ▼ markRootUpdated │ ▼ ensureRootIsScheduled │ ▼ ┌──────────────────────────────────────────────────────────┐ │ Prepare │ └──────────────────────────────────────────────────────────┘ renderRootSync / renderRootConcurrent │ ▼ prepareFreshStack │ ▼ createWorkInProgress(root.current, null) │ │ 这里只准备 HostRoot WIP │ 不是提前创建完整 WIP 树 ▼ ┌──────────────────────────────────────────────────────────┐ │ Render Phase │ │ 可中断、可重试、可放弃 │ └──────────────────────────────────────────────────────────┘ performUnitOfWork(WIP Fiber) │ ▼ beginWork(current, WIP) │ ├── HostRoot:处理根更新队列,得到根 Element │ ├── FunctionComponent:执行组件,得到 nextChildren │ ├── ClassComponent:调用 render,得到 nextChildren │ └── HostComponent:读取 props.children │ ▼ reconcileChildren │ │ current.child Fibers │ + │ 本层 nextChildren / Elements │ ↓ │ 匹配 key 和 type │ ↓ │ create / reuse child WIP Fiber │ ↓ │ 标记 Placement、Deletion 等 ▼ 返回 child WIP │ ▼ 继续 beginWork(child) │ ▼ 到达叶子节点 │ ▼ completeWork(leaf) │ ├── 新 Host 节点:创建离屏 DOM ├── 已有 Host 节点:复用 DOM,准备更新 ├── appendAllChildren:组装离屏 DOM 子树 └── bubbleProperties:向上汇总 flags │ ├── 有 sibling │ └── beginWork(sibling) │ └── 无 sibling └── completeWork(parent) │ ▼ HostRoot complete │ ▼ finishedWork │ │ 新 WIP Fiber Tree 已完成 │ root.current 仍然是旧树 ▼ ┌──────────────────────────────────────────────────────────┐ │ Commit Phase │ │ 将 Render 的结果应用到宿主环境 │ └──────────────────────────────────────────────────────────┘ before mutation │ ├── 读取 DOM 修改前状态 └── getSnapshotBeforeUpdate │ ▼ mutation │ ├── Placement:插入 / 移动 DOM ├── Update:更新属性 / 文本 ├── ChildDeletion:删除 DOM、卸载组件 ├── 解绑旧 ref └── 执行部分 effect 清理 │ ▼ root.current = finishedWork │ │ Fiber current 指针切换 │ 不是整棵 DOM Tree 替换 ▼ layout │ ├── useLayoutEffect setup ├── componentDidMount ├── componentDidUpdate └── 挂载新 ref │ ▼ 浏览器获得 Paint 机会 │ ▼ passive effects │ ├── 旧 useEffect cleanup └── 新 useEffect setup2、一句话流程
Update 负责提出变化 ↓ Schedule 负责决定什么时候计算 ↓ beginWork 负责边产生 Element、边协调、边构建 WIP Fiber ↓ completeWork 负责向上收尾、创建离屏 DOM、汇总 flags ↓ Commit mutation 负责修改已挂载 DOM ↓ root.current = finishedWork 负责切换 Fiber 当前版本 ↓ Layout Effects 读取最新 DOM ↓ Passive Effects 处理非布局型副作用