React V15 -> V16+ 概述
React 的理念是快速响应,v15 的架构分为两层:Reconciler 协调器和 renderer 渲染器,协调器负责找不同的组件,渲染器是将组件渲染到页面中。Reconciler -> renderer 是同步交替更新工作的。
Reconciler 采用递归同步方式更新子组件,递归一旦开始,就不能中断,而浏览器中 js 线程和渲染线程是互斥的,当 js 运行时间超过 16.67 ms 的浏览器一帧时间,页面操作就会无反应,画面会失帧,影响交互体验。
v16 采用 Fiber 架构,是异步的可中断更新,与原来相比增加了 Scheduler 调度器,其用来调度任务,将优先级高的任务传给 Reconciler,Reconciler 会将变化的虚拟 DOM 打上标记,整个组件都完成后,才会交给 Renderer 处理。[Scheduler ,Reconciler ] 是可中断的,在内存中,即使反复中断,用户也不感知。
Concurret Mode 是指 React 新架构带来的新特性,包括可中断更新,Suspense 组件等;v16 是让该模式成为可能,v17 是过渡版本,未来 v18 可以稳定使用。
开启方式:ReactDOM.unstable_createRoot / ReactDOM.createRoot;
说明
本文代码以 React v17.0.2 调试,步骤如下:
// 拉取源码
git clone https://gitee.com/mirrors/react.git
cd react
// 切到 v17.0.2 版本
git checkout v17.0.2
// yarn 下载依赖
// 下载慢的话,设置淘宝镜像源
// yarn config set registry https://registry.npm.taobao.org/
// yarn config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/
// yarn config set electron_mirror https://npm.taobao.org/mirrors/electron/ ...
yarn
// 打包
yarn build react/index,react/jsx,react-dom/index,scheduler --type=NODE
// link 改变包指向
cd build/node_modules/react
yarn link
cd build/node_modules/react-dom
yarn link
// 创建测试项目
npx create-react-app learn-react17
cd learn-react17
yarn link react react-dom
// 修改 index.js 开启 Concurrent Mode 试试
const root = ReactDOM.unstable_createRoot(document.getElementById('root'));
root.render(<App />);
yarn start
那么 Fiber 架构内部是什么样的呢?React 应用是怎么挂载和更新的呢?先来熟悉 React 内部的一些名词对象是什么:
Fiber 架构小知识
7kms画的架构分层图:
该图从源码包角度划分,解释(schduler、react-reconciler、react-dom)核心包的关系,其实也就是 React 的执行流程。我截下以下代码的执行栈图:
class App extends React.Component {
render() {
return (
<div className="App">
<p>1</p>
<p>2</p>
</div>
);
}
}
const root = ReactDOM.unstable_createRoot(document.getElementById('root'));
root.render(<App />);
legacy 模式:
legacy 模式少了调度,直接进 fiber 树构建循环;
两种模式构建 fiber 树逻辑是一样的,只不过 Concurrent 模式循环可中断;
任务调度循环和 fiber 树构造循环
任务循环里数据结构是小顶堆,是用来执行(reconciler 提供)回调,如 performSyncWorkOnRoot/performConcurrentWorkOnRoot;
fiber 循环是以树为数据结构,采取深度优先遍历方式生成 fiber,建立关系,形成树;
fiber 循环是任务循环的一部分,commitRoot 是任务循环的另外一部分;
即:更新 -> 任务1 -> fiber 循环 -> 任务2 -> fiber 循环 -> commitRoot。
React 内部的对象
React 从 JSX -> ReactElement -> Fiber 转换,内部数据结构是什么样的呢?
ReactElement
class App extends React.Component {
render() {
const ele = (
<div className="App">
<p>1</p>
<p>2</p>
</div>
);
console.log(ele)
return ele;
}
}
ReactJSXElement.js
内部执行如下:
jsxWithValidation -> jsxDEV -> ReactElement -> 生成 element
主要是 jsx 的合法校验,属性的处理,最后调用 ReactElement 构建 Element 对象返回。
Fiber 对象
ReactFiber.new.js
| 属性 | 含义 |
|---|---|
| tag | fiber 类型 |
| key | 与 ReactElement key 一致 |
| elementType | 一般与 ReactElement 组件的 type 一致 |
| type | 一般与fiber.elementType 一致 |
| stateNode | 关联的局部状态节点(HostComponent 指向 dom 节点,根节点(rootFiber)指向 FiberRoot,class 类型指向 class 实例) |
| return | 父节点 |
| child | 第一个子节点 |
| sibling | 下一个兄弟节点 |
| index | fiber 在兄弟节点中的索引,单节点默认为 0 |
| ref | 指向 ReactElement 组件上设置的 ref |
| pendingProps | 输入属性 |
| memoizedProps | 上一次生成子节点时用到的属性, 生成子节点之后保持在内存中. 向下生成子节点之前叫做pendingProps, 生成子节点之后会把pendingProps赋值给memoizedProps用于下一次比较.pendingProps和memoizedProps比较可以得出属性是否变动 |
| updateQueue | update 更新对象链式队列 |
| memoizedState | 上一次生成子节点之后保持在内存中的局部状态 |
| dependencies | 依赖的(context,evnets 等) |
| mode | 与 react 运行模式有关 |
| flags | 标记位 ReactFiberFlags.js |
| nextEffect | 下一个副作用的 fiber 节点 |
| firstEffect | 副作用链表中的第一个 fiber 节点 |
| lastEffect | 副作用链表中的最后一个 fiber 节点 |
| lanes | 本 fiber 节点所属的优先级,创建 fiber 的时候设置 |
| childLanes | 子节点所属的优先级 |
| alternate | 指向内存中的另一个 fiber,每个被更新过 fiber 节点在内存中都是成对出现(current 和 workInProgress) |
创建 fiber 方法 createFiberFromTypeAndProps
fiber.tag 与组件对应关系:
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const FundamentalComponent = 20;
export const ScopeComponent = 21;
export const Block = 22;
export const OffscreenComponent = 23;
export const LegacyHiddenComponent = 24;
update 和 updateQueue 对象
// ReactUpdateQueue.new.js
export type Update<State> = {|
lane: Lane,
// 0: UpdateState,1: ReplaceState,2: ForceUpdate, 3: CaptureUpdate
tag: 0 | 1 | 2 | 3,
payload: any, // 载荷, update 对象真正需要更新的数据, 根据场景可以设置成一个回调函数或者对象
callback: (() => mixed) | null, // 回调函数,commit 完后会调用
next: Update<State> | null, // 指向链表下一个,updateQueue 是环状链表,最后一个指向第一个对象
|};
type SharedQueue<State> = {|
// 指向即将输入的update队列. 在class组件中调用setState()之后, 会将新的 update 对象添加到这个队列中来
pending: Update<State> | null,
|};
export type UpdateQueue<State> = {|
baseState: State, // 此队列的基础 state
firstBaseUpdate: Update<State> | null, // 指向基础队列的队首
lastBaseUpdate: Update<State> | null, // 指向基础队列的队尾
shared: SharedQueue<State>, // 共享队列
effects: Array<Update<State>> | null, // 用于保存有callback回调函数的 update 对象, 在commit之后, 会依次调用这里的回调函数
|};
Hook 对象
// ReactFiberHooks.new.js
export type Hook = {|
memoizedState: any, // 内存状态, 用于输出成最终的fiber树
baseState: any, // 基础状态, 当Hook.queue更新过后, baseState也会更新
baseQueue: Update<any, any> | null, // 基础状态队列, 在reconciler阶段会辅助状态合并
queue: UpdateQueue<any, any> | null, // 指向一个 Update 队列
next: Hook | null, // 指向该 function 组件的下一个Hook对象, 使得多个Hook之间也构成了一个链表
|};
在函数组件中,fiber.memoizedState 指向 Hook 队列
Task 对象
Schduler 的任务队列 taskQueue 中,放着 task 对象。
// Scheduler.js
var newTask = {
id: taskIdCounter++, // 位移标识
callback, // 核心字段指向回调函数
priorityLevel, // 优先级
startTime, // task 开始时间(创建时间 + 延迟时间)
expirationTime, // 过期时间
sortIndex: -1, // 队列中次序,越小越靠前
};
优先级
React 内部有 fiber 优先级、调度优先级和优先级等级(负责转换前两种优先级)
lane 在 update 对象、fiber 对象 、渲染阶段中都有涉及。
补充:
fiber 优先级用 Lane 模型表示,相比原来的 expirationTime 模型,更方便表达和计算。
Lane 定义为二进制变量,长度为 31 位,没占用一位表示一个任务,占用的比特位越低,优先级越高,即下面 SyncLane 优先级最高,OffscreenLane 优先级最低。
Lanes 是占用多个比特位,表示批的概念,越低优先级,占用位数越多。
// ReactFiberLane.js
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;
export const InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011000;
const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011000000;
export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000;
export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111000000000;
const TransitionHydrationLane: Lane = /* */ 0b0000000000000000001000000000000;
const TransitionLanes: Lanes = /* */ 0b0000000001111111110000000000000;
const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;
export const SomeRetryLane: Lanes = /* */ 0b0000010000000000000000000000000;
export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000;
const NonIdleLanes = /* */ 0b0000111111111111111111111111111;
export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
const IdleLanes: Lanes = /* */ 0b0110000000000000000000000000000;
export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
双缓存技术
React 内部维护两颗 Fiber 树,屏幕显示当前内容的 current 树,和内存中构建的 workInprogress 树,FiberRoot 通过切换两个树来更新展示 DOM。
Schdeuler 调度
时间分片
分割时间,让出主线程,把控制权还给浏览器。
先看浏览器原生 requestIdleCallback,利用浏览器空闲时间执行任务。例如:
console.time();
var Work = {
unit: 1000,
onOneUnit() {
for (let i = 0; i <= 500000; i++) { }
},
onSyncUnit() {
let _u = 0;
while (_u < Work.unit) {
Work.onOneUnit();
_u++;
}
},
onAsyncUnit() {
const free_time = 1;
let _u = 0;
function cb(deadline) {
while (_u < Work.unit && deadline.timeRemaining() > free_time) {
Work.onOneUnit();
_u++;
}
if (_u >= Work.unit) {
return;
}
window.requestIdleCallback(cb);
}
window.requestIdleCallback(cb);
}
}
Work.onSyncUnit();
// Work.onAsyncUnit();
console.timeEnd();
执行 onSyncUnit ,未经过 requestIdleCallback 处理,js 线程占据大量时间:
改为 requestIdleCallback 处理,原来的长任务,利用浏览器空闲时间分段执行,不阻塞渲染线程:
React 如何实现的呢?
先看下调度的整体流程:
代码如下:
// Scheduler.js
function unstable_scheduleCallback(priorityLevel, callback, options) {
var currentTime = getCurrentTime();
var startTime;
if (typeof options === 'object' && options !== null) {
...
} else {
startTime = currentTime;
}
var timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT;
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT;
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
}
var expirationTime = startTime + timeout;
var newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
if (enableProfiling) {
newTask.isQueued = false;
}
if (startTime > currentTime) {
...
} else {
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
if (enableProfiling) {
markTaskStart(newTask, currentTime);
newTask.isQueued = true;
}
// Schedule a host callback, if needed. If we're already performing work,
// wait until the next time we yield.
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
return newTask;
}
// SchedulerHostConfig.default.js
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
requestHostCallback = function(callback) {
// performWorkUntilDeadline 中调用
scheduledHostCallback = callback;
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
port.postMessage(null);
}
};
const performWorkUntilDeadline = () => {
if (scheduledHostCallback !== null) {
const currentTime = getCurrentTime();
// yieldInterval 5
deadline = currentTime + yieldInterval;
const hasTimeRemaining = true;
try {
const hasMoreWork = scheduledHostCallback(
hasTimeRemaining,
currentTime,
);
if (!hasMoreWork) {
isMessageLoopRunning = false;
scheduledHostCallback = null;
} else {
port.postMessage(null);
}
} catch (error) {
port.postMessage(null);
throw error;
}
} else {
isMessageLoopRunning = false;
}
needsPaint = false;
};
初始化时 scheduleCallback 内部调用 unstable_scheduleCallback,其中入参 callback 为 fiber 构建入口函数 performConcurrentWorkOnRoot / performSyncWorkOnRoot,调度最终会执行这两个函数。
unstable_scheduleCallback 内部会给每次更新创建 task 对象,task.callback 就是 fiber 构建入口函数。能执行的 task 放在 taskQueue (小顶堆队列),执行requestHostCallback(flushWork),requestHostCallback 内部调用 port2.postMessage,触发 port1 的监听函数 performWorkUntilDeadline,MessageChannel 是宏任务,是异步的,所以调度流程也是异步。
performWorkUntilDeadline 中的scheduledHostCallback,就是 flushWork,调用后如果还有任务,循环调 performWorkUntilDeadline,没有任务就结束。
React 的时间分片通过 workLoop 中每次 while 循环退出就是一次时间分片,有两种情况:
1、队列全部清空;
2、task.callback 执行前会检查任务有没超时(没有过期且有剩余时间或者不要让出主线程,才会执行),若超时循环中断,等待下一次调用;
可中断渲染
在时间分片基础上,fiber 构造循环时通过 shouldYield,检查是否超时,若超时,循环中断,返回新的函数(workLoop 中的continuationCallback)等待下一次回调,完成未完成的 fiber 树构建。
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
React 挂载流程
1、构建 fiberRoot 和 rootFiber
- legacy 模式逻辑
- Concurrent 模式逻辑
2、调用 updateContainer,legacy 直接构建 fiber 树循环;Concurrent 进入调度,回调进入 fiber 树构建循环;
3、fiber 树构造循环
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
fiber 树构建从 rootFiber 开始深度遍历,每个 fiber 处理,先执行递阶段 beginWork,当遍历到叶子节点时,执行 归阶段completeUnitOfWork ,若有兄弟节点,执行兄弟节点的递和归,没有,向上归到父节点,一直到 rooFiber 节点结束。
beginWork 做了什么?
-
根据 ReactElement 对象创建 fiber ,最终构造成 fiber 树(通过 return sibling 指针设置);
-
设置 fiber.flags ,completeWork 会处理;
-
设置 fiber.statNode 局部状态;
updateXXX 逻辑?
- 根据 fiber.pendingProps,fiber.updateQueue 等输入状态,计算 fiber.memoizedState 作为输出状态;
- 获取下级 ReactElement 对象;
- 根据 ReactElement 对象,调用 reconcilerChildren 生成 fiber 子节点;
completeUnitOfWork 逻辑
-
completeWork处理- 给 fiber 节点(tag = HostComponent,HostText)创建 DOM 实例,设置 fiber.stateNode 状态;
- 为 DOM 节点设置属性,绑定事件;
- 设置 fiber.flags;
-
把当前 fiber 对象副作用队列添加到父节点的副作用队列之后,更新父节点 firstEffect、lastEffect 指针;
-
识别 beginWork 设置的 fiber.flags,判断当前 fiber 是否有副作用(增、删、改),有的话将当前 fiber 加入到父节点 effects 队列中,等待 commit 阶段处理;
4、树渲染
commitRoot 渲染做了啥?
渲染前: 设置全局状态,重置全局变量,再次更新副作用队列(只更新 fiberRoot.finishedWork);
渲染: 分三阶段,即 DOM 更新前、更新中、更新后,主要是处理副作用,更新 DOM;
commitBeforeMutationEffects处理副作用队列带有Snapshot,Passive标记的fiber节点commitMutationEffects处理副作用队列中带有ContentReset,Ref,Placement,Update,Deletion,Hydrating标记的fiber节点commitLayoutEffects处理副作用队列中带有Update, Callback,ref标记的fiber节点
渲染后:
清除副作用队列,将链表全部拆开,更新时会被垃圾回收机制回收;
检查更新,异步任务 ensureRootIsScheduled 处理,同步任务 flushSyncCallbackQueue ,再次进入 fiber 树构造循环;
举个例子:
class App extends React.Component {
state = {
list: ['A', 'B', 'C'],
};
onChange = () => {
this.setState({ list: ['C', 'A', 'X'] });
};
componentDidMount() {
console.log(`App Mount`);
}
render() {
return (
<div>
<Header />
<button onClick={this.onChange}>change</button>
<div className="content">
{this.state.list.map(item => (
<p key={item}>{item}</p>
))}
</div>
</div>
);
}
}
class Header extends React.PureComponent {
render() {
return (
<>
<h1>title</h1>
</>
);
}
}
performUnitOfWork 执行:
performUnitOfWork 第 1 次执行:
workInProgress 指向 rootFiber,current 为 rootFiber;
执行 updateHostRoot -> reconcileChildren 会返回 chid: App fiber,设置 App fiber.flags 为 Placement;
workInProgress 指向 App fiber;
performUnitOfWork 第 2 次执行:
workInProgress 指向 App,current 为 null;
执行 updateClassComponent 设置 App fiber.flags 为 Placement|Update,这里忽略 dev tools 使用的 PerformedWork ,reconcileChildren 会返回 chid: div fiber;
workInProgress 指向 div fiber;
performUnitOfWork 第 3 次执行:
workInProgress 指向 div,current 为 null;
执行 updateHostComponent ; reconcileChildren 会处理所有子节点的关系,返回 chid: Header fiber;
workInProgress 指向 Header fiber;
performUnitOfWork 第 4 次执行:
workInProgress 指向 Header,current 为 null;
执行 updateClassComponent 初始化实例 ; reconcileChildren 返回 chid: h1 fiber;
workInProgress 指向 h1 fiber;
performUnitOfWork 第 5 次执行:
workInProgress 指向 h1,current 为 null;
执行 updateHostComponent 是文本 ; reconcileChildren 返回 null;
进入completeUnitOfWork,一次循环没有兄弟节点,向上回溯,workInProgress 指向 header fiber;二次循环,有兄弟节点workInProgress 指向 button fiber,结束循环;
performUnitOfWork 第 6 次执行:
workInProgress 指向 button,current 为 null;
执行 updateHostComponent 是文本 ; reconcileChildren 返回 null;
进入completeUnitOfWork,一次循环,workInProgress 指向兄弟节点 div fiber,结束循环;
performUnitOfWork 第 7,8,9 次执行和上面差不多;
performUnitOfWork 第 10 次执行:
workInProgress 指向 p-c,current 为 null;
执行 updateHostComponent 是文本 ; reconcileChildren 返回 null;
进入completeUnitOfWork, 当 completeWork不为 null,一直循环,向上回溯,如有副作用,也会跟着传到上级节点中,到 rootFiber 结束循环。
进入 commit 阶段:
将 rootFiber 副作用添加到队列末尾,渲染,切换 fiberRoot.current 指向 finishedWork ,清理,检查更新。
副作用队列如下:
performUnitOfWork 初次创建执行流程汇总:
| performUnitOfWork 执行 | workInProgress | current (workInProgress.alternate) | flags | next | 更改workInProgress |
|---|---|---|---|---|---|
| 1 | rootFiber | rootFiber | App: Placement | App | App |
| 2 | App | null | App: Placement |Update | div | div |
| 3 | div | null | Header | Header | |
| 4 | Header | null | h1 | h1 | |
| 5 | h1 | null | null | Header->button | |
| 6 | button | null | null | div | |
| 7 | div | null | p-A | p-A | |
| 8 | p-A | null | null | p-B | |
| 9 | p-B | null | null | p-C | |
| 10 | p-C | null | null | div->div->App->rootFiber(设置flag: Snapshot) |
React 更新流程
更新和初次创建流程都会进入 scheduleUpdateOnFiber,所以调用链路是一样的,legacy 模式同样是:performSyncWorkOnRoot -> renderRootSync -> workLoopSync,所以主要看有哪些不同。
- 更新的方式:class 组件 setState,函数组件 hook 的 dispatchAction,ReactDOM.render ;它们调用都会进入
scheduleUpdateOnFiber。
更新时,scheduleUpdateOnFiber 中的markUpdateLaneFromFiberToRoot 会找到 rootFiber 节点,它会设置当前 fiber.lanes ,设置父路径上所有节点的 fiber.childLanes(包括 fiber.alternate 上的),通过设置 fiber.lanes 和 fiber.childLanes 辅助判断子树是否需要更新;
- 更新过程中 current 不为空,所以相比初次创建,走不为空的逻辑,还有节点复用函数
bailoutOnAlreadyFinishedWork,还有reconcileChildren调和函数会比较,判断是否复用节点逻辑也不同。
还是上面初次创建例子,点击 change 按钮 setState 更新
scheduleUpdateOnFiber 中的 markUpdateLaneFromFiberToRoot返回 root,
prepareFreshStack,重置 workInProgress.flags和workInProgress.effects,workInProgress.child = current.child,节点关系如下:
循环构建 fiber 树:
performUnitOfWork 第 1 次执行:
workInProgress 指向 rootFiber,current 为页面 rootFiber;
进入 bailoutOnAlreadyFinishedWork -> cloneChildFibers,返回 App fiber(丢弃原 fiber 副作用和 flags,其它属性保留);
performUnitOfWork 第 2 次执行:
workInProgress 指向 App Fiber,current 为页面 App Fiber;
App 实例已创建,current 不为空,会走不同的逻辑,返回 div Fiber;
performUnitOfWork 第 3 次执行:
workInProgress 指向 div Fiber,current 为页面 div Fiber;
返回 Header Fiber;
performUnitOfWork 第 4 次执行:
workInProgress 指向 Header Fiber,current 不为空;
执行 bailoutOnAlreadyFinishedWork ,子节点也不用更新,返回 null;
进入 completeUnitOfWork,有兄弟节点,结束循环,workInProgress 指向 button Fiber ;
performUnitOfWork 第 5 次执行:
workInProgress 指向 button,current 为页面 button fiber;
执行 updateHostComponent 是文本 ; reconcileChildren 返回 null;
进入 completeUnitOfWork,一次循环,workInProgress 指向兄弟节点 div fiber,结束循环;
performUnitOfWork 第 6 次执行:
workInProgress 指向 div fiber,current 为页面 div fiber;
在 updateHostComponent()函数中, 调用 reconcilerChildren() 生成下级子节点(这里 A、X flags: Placement,原来的 B 被标记 Deletion,添加到父节点 副作用队列中);
返回 p-C fiber;
performUnitOfWork 第 7 次执行:
workInProgress 指向 p-C fiber,current 为页面 p-C fiber;
执行 updateHostComponent() 函数,是文本,返回 null,进入 completeUnitOfWork ,不用打标记,返回兄弟节点 p-A fiber;
performUnitOfWork 第 8 次执行:
workInProgress 指向 p-A fiber,current 为页面 p-A fiber;
执行 updateHostComponent() 函数,是文本,返回 null,进入 completeUnitOfWork ,因为 workInProgress.stateNode 不为null,不用打标记;A 有副作用 (Placement),加到父节点队列后;
返回兄弟节点 p-X fiber,结束循环;
performUnitOfWork 第 9 次执行:
workInProgress 指向 p-X fiber,current 为页面 null;
执行 updateHostComponent() 函数,是文本,返回 null,进入 completeUnitOfWork ,因为 workInProgress.stateNode 为null,创建 DOM 实例;X 有副作用 (Placement),加到父节点队列后;
没有兄弟节点向上回溯:div -> div -> App -> rooFiber,过程中将本节点副作用向上传递,到 rootFiber 循环结束;