React17 流程分析

807 阅读16分钟

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画的架构分层图:

宏观包结构.png

该图从源码包角度划分,解释(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 />);

react-01.png

legacy 模式:

react-02.png

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;
  }
}

image.png

ReactJSXElement.js

内部执行如下:

jsxWithValidation -> jsxDEV -> ReactElement -> 生成 element

主要是 jsx 的合法校验,属性的处理,最后调用 ReactElement 构建 Element 对象返回。

Fiber 对象

ReactFiber.new.js

属性含义
tagfiber 类型
key与 ReactElement key 一致
elementType一般与 ReactElement 组件的 type 一致
type一般与fiber.elementType 一致
stateNode关联的局部状态节点(HostComponent 指向 dom 节点,根节点(rootFiber)指向 FiberRoot,class 类型指向 class 实例)
return父节点
child第一个子节点
sibling下一个兄弟节点
indexfiber 在兄弟节点中的索引,单节点默认为 0
ref指向 ReactElement 组件上设置的 ref
pendingProps输入属性
memoizedProps上一次生成子节点时用到的属性, 生成子节点之后保持在内存中. 向下生成子节点之前叫做pendingProps, 生成子节点之后会把pendingProps赋值给memoizedProps用于下一次比较.pendingPropsmemoizedProps比较可以得出属性是否变动
updateQueueupdate 更新对象链式队列
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 优先级、调度优先级和优先级等级(负责转换前两种优先级)

image.png 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 线程占据大量时间:

image-20211023212756309.png

改为 requestIdleCallback 处理,原来的长任务,利用浏览器空闲时间分段执行,不阻塞渲染线程:

image-20211023213003013.png

React 如何实现的呢?

先看下调度的整体流程:

image.png

代码如下:

// 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 的监听函数 performWorkUntilDeadlineMessageChannel 是宏任务,是异步的,所以调度流程也是异步。

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 模式逻辑

image.png

  • Concurrent 模式逻辑

image.png

2、调用 updateContainer,legacy 直接构建 fiber 树循环;Concurrent 进入调度,回调进入 fiber 树构建循环;

image-20211026160137216.png

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 处理副作用队列带有 SnapshotPassive标记的fiber节点
  • commitMutationEffects 处理副作用队列中带有 ContentResetRefPlacementUpdateDeletionHydrating标记的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 执行:

react-mount.png

performUnitOfWork 第 1 次执行:

workInProgress 指向 rootFibercurrentrootFiber;

执行 updateHostRoot -> reconcileChildren 会返回 chid: App fiber,设置 App fiber.flagsPlacement;

workInProgress 指向 App fiber;

performUnitOfWork 第 2 次执行:

workInProgress 指向 Appcurrentnull;

执行 updateClassComponent 设置 App fiber.flagsPlacement|Update,这里忽略 dev tools 使用的 PerformedWorkreconcileChildren 会返回 chid: div fiber;

workInProgress 指向 div fiber;

performUnitOfWork 第 3 次执行:

workInProgress 指向 divcurrentnull;

执行 updateHostComponent ; reconcileChildren 会处理所有子节点的关系,返回 chid: Header fiber;

workInProgress 指向 Header fiber;

performUnitOfWork 第 4 次执行:

workInProgress 指向 Headercurrentnull;

执行 updateClassComponent 初始化实例 ; reconcileChildren 返回 chid: h1 fiber;

workInProgress 指向 h1 fiber;

performUnitOfWork 第 5 次执行:

workInProgress 指向 h1currentnull;

执行 updateHostComponent 是文本 ; reconcileChildren 返回 null;

进入completeUnitOfWork,一次循环没有兄弟节点,向上回溯,workInProgress 指向 header fiber;二次循环,有兄弟节点workInProgress 指向 button fiber,结束循环;

performUnitOfWork 第 6 次执行:

workInProgress 指向 buttoncurrentnull;

执行 updateHostComponent 是文本 ; reconcileChildren 返回 null;

进入completeUnitOfWork,一次循环,workInProgress 指向兄弟节点 div fiber,结束循环;

performUnitOfWork 第 7,8,9 次执行和上面差不多;

performUnitOfWork 第 10 次执行:

workInProgress 指向 p-ccurrentnull;

执行 updateHostComponent 是文本 ; reconcileChildren 返回 null;

进入completeUnitOfWork, 当 completeWork不为 null,一直循环,向上回溯,如有副作用,也会跟着传到上级节点中,到 rootFiber 结束循环。

进入 commit 阶段:

将 rootFiber 副作用添加到队列末尾,渲染,切换 fiberRoot.current 指向 finishedWork ,清理,检查更新。

副作用队列如下:

image.png

performUnitOfWork 初次创建执行流程汇总:

performUnitOfWork 执行workInProgresscurrent (workInProgress.alternate)flagsnext更改workInProgress
1rootFiberrootFiberApp: PlacementAppApp
2AppnullApp: Placement |Updatedivdiv
3divnullHeaderHeader
4Headernullh1h1
5h1nullnullHeader->button
6buttonnullnulldiv
7divnullp-Ap-A
8p-Anullnullp-B
9p-Bnullnullp-C
10p-Cnullnulldiv->div->App->rootFiber(设置flag: Snapshot)

React 更新流程

更新和初次创建流程都会进入 scheduleUpdateOnFiber,所以调用链路是一样的,legacy 模式同样是:performSyncWorkOnRoot -> renderRootSync -> workLoopSync,所以主要看有哪些不同。

  • 更新的方式:class 组件 setState,函数组件 hook 的 dispatchAction,ReactDOM.render ;它们调用都会进入 scheduleUpdateOnFiber

image.png

更新时,scheduleUpdateOnFiber 中的markUpdateLaneFromFiberToRoot 会找到 rootFiber 节点,它会设置当前 fiber.lanes ,设置父路径上所有节点的 fiber.childLanes(包括 fiber.alternate 上的),通过设置 fiber.lanes 和 fiber.childLanes 辅助判断子树是否需要更新;

  • 更新过程中 current 不为空,所以相比初次创建,走不为空的逻辑,还有节点复用函数bailoutOnAlreadyFinishedWork,还有reconcileChildren调和函数会比较,判断是否复用节点逻辑也不同。

还是上面初次创建例子,点击 change 按钮 setState 更新

scheduleUpdateOnFiber 中的 markUpdateLaneFromFiberToRoot返回 root,

prepareFreshStack,重置 workInProgress.flagsworkInProgress.effectsworkInProgress.child = current.child,节点关系如下:

image.png

循环构建 fiber 树:

update-react.png

performUnitOfWork 第 1 次执行:

workInProgress 指向 rootFibercurrent 为页面 rootFiber;

进入 bailoutOnAlreadyFinishedWork -> cloneChildFibers,返回 App fiber(丢弃原 fiber 副作用和 flags,其它属性保留);

performUnitOfWork 第 2 次执行:

workInProgress 指向 App Fibercurrent 为页面 App Fiber;

App 实例已创建,current 不为空,会走不同的逻辑,返回 div Fiber;

performUnitOfWork 第 3 次执行:

workInProgress 指向 div Fibercurrent 为页面 div Fiber;

返回 Header Fiber;

performUnitOfWork 第 4 次执行:

workInProgress 指向 Header Fiber,current 不为空;

执行 bailoutOnAlreadyFinishedWork ,子节点也不用更新,返回 null;

进入 completeUnitOfWork,有兄弟节点,结束循环,workInProgress 指向 button Fiber ;

performUnitOfWork 第 5 次执行:

workInProgress 指向 buttoncurrent 为页面 button fiber;

执行 updateHostComponent 是文本 ; reconcileChildren 返回 null;

进入 completeUnitOfWork,一次循环,workInProgress 指向兄弟节点 div fiber,结束循环;

performUnitOfWork 第 6 次执行:

workInProgress 指向 div fibercurrent 为页面 div fiber;

updateHostComponent()函数中, 调用 reconcilerChildren() 生成下级子节点(这里 A、X flags: Placement,原来的 B 被标记 Deletion,添加到父节点 副作用队列中);

返回 p-C fiber;

performUnitOfWork 第 7 次执行:

workInProgress 指向 p-C fibercurrent 为页面 p-C fiber;

执行 updateHostComponent() 函数,是文本,返回 null,进入 completeUnitOfWork ,不用打标记,返回兄弟节点 p-A fiber

performUnitOfWork 第 8 次执行:

workInProgress 指向 p-A fibercurrent 为页面 p-A fiber;

执行 updateHostComponent() 函数,是文本,返回 null,进入 completeUnitOfWork ,因为 workInProgress.stateNode 不为null,不用打标记;A 有副作用 (Placement),加到父节点队列后;

返回兄弟节点 p-X fiber,结束循环;

performUnitOfWork 第 9 次执行:

workInProgress 指向 p-X fibercurrent 为页面 null;

执行 updateHostComponent() 函数,是文本,返回 null,进入 completeUnitOfWork ,因为 workInProgress.stateNode 为null,创建 DOM 实例;X 有副作用 (Placement),加到父节点队列后;

没有兄弟节点向上回溯:div -> div -> App -> rooFiber,过程中将本节点副作用向上传递,到 rootFiber 循环结束;

参考

图解 React

React 技术揭秘