React 实现原理
实现 jsx
1 | let element = ( |
编译后
如何实现
jsx-dev-runtime.js
1
2export { jsxDEV } from "./jsx/ReactJSXElement";
ReactJSXElement.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57import hasOwnProperty from '../../shared/hasOwnProperty';
// dom 类型
import { REACT_ELEMENT_TYPE } from '../../shared/ReactSymbols';
const RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true
};
function hasValidKey(config) {
return config.key !== undefined;
}
function hasValidRef(config) {
return config.ref !== undefined;
}
// ReactElement 创建
function ReactElement(type, key, ref, props, owner) {
return {
// 这个标签允许我们唯一地将其标识为React元素
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
ref,
props,
}
}
export function jsxDEV(type, config) {
// 提取保留名称
const props = {};
let key = null;
let ref = null;
if(hasValidKey(config)) {
key = config.key;
}
if(hasValidRef(config)) {
ref = config.ref;
}
for(const propName in config) {
if(hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
return ReactElement(type, key, ref, props)
}hasOwnProperty.js
1
2
3const { hasOwnProperty } = Object.prototype;
export default hasOwnProperty;ReactSymbols.js
1
2// 用于标记类似 ReactElement 类型的符号。
export const REACT_ELEMENT_TYPE = Symbol.for('react.element');
根节点和根 fiber
- 需要给 react 提供一个根节点,之后每个节点都是渲染在根节点内部的。
1 | const root = createRoot(document.getElementById('root')) |
- 根 fiber 可以通俗理解为一个装着所有虚拟 dom 的容器,每个虚拟 dom 又单独对应一个 fiber,
- 渲染可以以单个 fiber 为单位暂停 / 恢复。
- 需要创建两个根
fiber
去相互替换展示。
创建根节点
更改 main.jsx
1 | import { createRoot } from "react-dom/client" |
首先要建一个 FiberRootNode
根节点,也就是所有 DOM 的根,本质就是 div#root
。
根节点和 fiber
关系:
FiberRootNode
和 HostRootFiber
中间使用 current
相连;
HostRootFiber
和 FiberRootNode
中间使用 stateNode
相连。
实现 createRoot
分这么多文件的主要是因为很多其他逻辑要处理,暂时都给省略了。虽然比较绕,但其实本质就是把 div#root
做了几层包装。
client.js
1
export { createRoot } from "./src/client/ReactDOMRoot";
ReactDOMRoot.js
1
2
3
4
5
6
7
8
9
10
11
12import { createContainer } from "react-reconciler/src/ReactFiberReconciler";
function ReactDOMRoot(internalRoot) {
this._internalRoot = internalRoot;
}
// 创建一个根 实际就是一个被包装过的真实DOM节点
// container: div#root
export function createRoot(container) {
// 1. 创建容器 6. 接收到有#root的容器
const root = createContainer(container);
// 7. 容器传给 ReactDOMRoot
return new ReactDOMRoot(root);
}ReactFiberReconciler.js
1
2
3
4
5
6import { createFiberRoot } from "./ReactFiberRoot";
// 创建容器 containerInfo: 容器信息
export function createContainer(containerInfo) {
// 2
return createFiberRoot(containerInfo);
}ReactFiberRoot.js
1
2
3
4
5
6
7
8
9
10
11function FiberRootNode(containerInfo) {
// 4. 把DOM节点放到容器
this.containerInfo = containerInfo
}
export function createFiberRoot(containerInfo) {
// 3. 创建根容器
const root = new FiberRootNode(containerInfo);
// 这个位置在下一步要创建 FiberRoot
// 5. 把容器返回出去
return root;
}
现在根节点 FiberRootNode
创建好了,最后 root
的打印结果:
fiber
在创建根 fiber 之前先了解一下 fiber
为什么需要有 fiber?
- react 以前没有 fiber 整个计算过程不能暂停,会导致时间过长
- 浏览器刷新频率为 60Hz, 大概 16.6 毫秒渲染一次,而 JS 线程和渲染线程是互斥的,所以如果 JS 线程执行任务时间超过 16.6ms 的话,就会导致掉帧、卡顿,解决方案就是 React 利用空闲的时间进行更新,不影响渲染进行的渲染
- 把一个耗时任务切分成一个个小任务,分布在每一帧里。这个的方式就叫时间切片
我们需要把渲染变成一个可中断,可暂停,可恢复的过程。
注:可以去搜一下 requestIdleCallback API ,react 封装了一个类似的方法让每帧时间固定 5ms。
什么是 fiber?
Fiber 是一个执行单元
Fiber 是一个执行单元,每次执行完一个执行单元,React 就会检查现在还剩多少时间,如果没有时间就将控制权让出去。
react 中一帧的过程:
Fiber 是一种数据结构
React 目前的做法是使用链表,每个虚拟节点内部表示为一个 Fiber
从顶点开始遍历
如果有第一个儿子,先遍历第一个儿子
如果没有第一个儿子,标志着此节点遍历完成
如果有弟弟遍历弟弟
如果有没有下一个弟弟,返回父节点标识完成父节点遍历,如果有叔叔遍历叔叔
没有父节点遍历结束遍历结构:
遍历过程:
创建根 fiber
真实 DOM 需要一个根容器,fiber
同样需要一个根 fiber
。
相当于每个虚拟 DOM 都会创建一个对应的 Fiber
,再创建真实 DOM
虚拟 DOM => Fiber => 真实 DOM
在刚刚创建 FiberRootNode 的函数里去创建 HostRootFiber
并互相指向对方。
ReactFiberRoot.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import { createHostRootFiber } from "./ReactFiber";
function FiberRootNode(containerInfo) {
this.containerInfo = containerInfo;
}
export function createFiberRoot(containerInfo) {
// 之前创建的根节点容器
const root = new FiberRootNode(containerInfo);
// 1. 创建根fiber. hostRoot就是根节点dev#root
// 未初始化的fiber
const uninitializedFiber = createHostRootFiber();
// 当前渲染页面的fiber.
// 6. 根容器的current指向当前的根fiber
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
return root;
}ReactFiber.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40// 3. 工作标签
import { HostRoot } from "./ReactWorkTags";
// 5. 副作用标识
import { NoFlags } from "./ReactFiberFlags";
export function FiberNode(tag, pendingProps, key) {
this.tag = tag;
this.key = key;
this.type = null; // fiber类型, 来自于虚拟DOM节点的type (span h1 p)
this.stateNode = null; // 此fiber对应的真实DOM节点
this.return = null; // 指向父节点
this.child = null; // 指向第一个子节点
this.sibling = null; // 指向弟弟
this.pendingProps = pendingProps; // 等待生效的属性
this.memoizedProps = null; // 已经生效的属性
// 虚拟DOM会提供pendingProps给创建fiber的属性,等处理完复制给memoizedProps
// 每个fiber还会有自己的状态,每一种fiber状态存的类型都不一样
// 比如:类组件对应的fiber存的就是实例的状态,HostRoot存的就是要渲染的元素
this.memoizedState = null;
// 每个fiber可能还有自己的更新队列
this.updateQueue = null;
// 5. "./ReactFiberFlags"
this.flags = NoFlags; // 副作用标识,表示对此fiber节点进行何种操作
this.subtreeFlags = NoFlags; // 子节点对应的副作用标识
this.alternate = null; // 轮替 (缓存了另一个fiber节点实例) diff时用
}
export function createFiberNode(tag, pendingProps, key) {
return new FiberNode(tag, pendingProps, key);
}
export function createHostRootFiber() {
return createFiberNode(HostRoot, null, null);
}ReactWorkTags.js
1
2
3
4
5
6
7// 每种虚拟DOM都会对应自己的fiber的类型
// 根Fiber的Tag
export const HostRoot = 3; // 根节点
export const HostComponent = 5; // 原生节点 span div p
export const HostText = 6; // 纯文本节点
// ...ReactFiberFlags.js
1
2
3
4
5
6
7// 没有任何操作
export const NoFlags = 0b000000000000000000000000000000;
// 插入
export const Placement = 0b000000000000000000000000000010;
// 更新
export const Update = 0b000000000000000000000000000100;看最后 root 的打印结果:根 fiber 和节点容器互相指向
current指的是当前根容器正在显示或者已经渲染好的fiber树
react 采用了双缓存区的技术,可以把将要显示的图片绘制在缓存区中,需要展示的时候直接拿来替换掉。 alternate 轮替。
创建队列
打开 ReactFiberRoot.js 文件,在 return root 之前加一行代码,给根 fiber 加上一个更新队列,之后更新渲染任务都是放到这个队列里面。
ReactFiberRoot.js
1
2
3
4+ import { initialUpdateQueue } from "./ReactFiberClassUpdateQueue";
...
+ initialUpdateQueue(uninitializedFiber);
return root;ReactFiberClassUpdateQueue.js
1
2
3
4
5
6
7
8
9
10export function initialUpdateQueue(fiber) {
// 创建一个更新队列
// pending 是循环链表
const queue = {
shared: {
pending: null,
}
}
fiber.updateQueue = queue;
}
如下图所示在 fiber 树中增加了 updateQueue 队列
构建轮替的根 fiber
fiber 是怎么运作的
为什么要轮替在上一篇已经说过了,这一篇写一下fiber的单项循环链表。
假如我们有一个 jsx 的 dom 结构
1 | let element = ( |
在以前没有用 fiber 渲染是这样的,这个渲染方式是递归渲染如果数据很多就可能会卡顿。
1 | let vDom = { |
下面是 fiber 的渲染方式,可以中断、暂停、恢复渲染。深度优先
1 | // 把虚拟DOM构建成Fiber树 |
上面的这些代码是预先熟悉一下,fiber 是怎么运行,下面正式进入构建的代码逻辑。
队列的单向链表
在 main.js 中增加下面代码
1
root.render(element)
ReactDOMRoot.js
1
2
3
4
5
6
7import { updateContainer } from '../react-reconciler/src/ReactFiberReconciler';
...
ReactDomRoot.prototype.render = function (children) {
// 1. 获取容器
const root = this._internalRoot;
updateContainer(children, root);
}ReactFiberReconciler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import { createUpdate, enqueueUpdate } from './ReactFiberClassUpdateQueue';
...
/**
* 更新容器, 把虚拟DOM变成真实DOM 插入到container容器中
* @param {*} element 虚拟DOM
* @param {*} container 容器 FiberRootNode
*/
export function updateContainer(element, container) {
// 获取根fiber
const current = container.current;
// 创建更新队列
const update = createUpdate();
update.payload = {element};
// 3. 把此更新任务对象添加到current这个根Fiber的更新队列里
let root = enqueueUpdate(current, update);
console.log(root);
}ReactFiberClassUpdateQueue.js
1 | import { markUpdateLaneFromFiberToRoot } from './ReactFiberConcurrentUpdate' |
冒泡获取根节点容器
ReactFiberConcurrentUpdate.js
1 | import { HostRoot } from './ReactWorkTags'; |
调度更新
到目前为止更新对象已经添加到了根 fiber 的更新队列上,现在需要开始进行调度更新。
ReactFiberReconciler.js
1
2
3
4
5
6
7import { scheduleUpdateOnFiber } from './ReactFiberWorkLoop'
...
export function updateContainer(element, container) {
...
+ scheduleUpdateOnFiber(root);
}ReactFiberWorkLoop.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14import { scheduleCallback } from './scheduler';
export function scheduleUpdateOnFiber(root) {
ensureRootIsScheduled(root);
}
export function ensureRootIsScheduled(root) {
// 告诉浏览器要执行performConcurrentWorkOnRoot 参数定死为root
scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}
function performConcurrentWorkOnRoot(root) {
console.log(root, 'performConcurrentWorkOnRoot');
}src/scheduler/index.js
1
export * from './src/forks/Scheduler';
src/forks/Scheduler.js
1
2
3
4// 此处后面会实现优先级队列
export function scheduleCallback(callback) {
requestIdleCallback(callback);
}打印 FiberRootNode
工作循环
我们已经创建好一个根节点容器和一个空的根 fiber(黑色部分),在图中看到还有一个正在构建中的根 fiber。
根节点的 current 指的是当前的根 fiber,是会和构建中的根 fiber 轮替工作(双缓冲),现在需要构建一个新的根 fiber 并且把 fiber 树写在里面。
一个是表示当前页面已经渲染完成的 fiber 树,一个是正在构建中还没有生效、更没有更新到页面的 fiber 树
建立新的 hostRootFiber
ReactFiberWorkLoop.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27import { creatWorkInProgress } from "./ReactFiber";
// 正在进行中的工作
let workInProgress = null
//...
/**
* (被告知浏览器确保执行的函数)
* 根据当前的fiber节点构建fiber树, 创建真实的dom节点, 插入到容器
* @param {*} root
*/
function performConcurrentWorkOnRoot(root) {
// 1. 初次渲染的时候以同步方式渲染根节点, 因为要尽快展示 (初始化)
renderRootSync(root);
}
function prepareFreshStack(root) {
// 5. 根据老fiber构建新fiber (初始化)
workInProgress = creatWorkInProgress(root.current);
}
function renderRootSync(root) {
// 2. 先构建了一个空的栈
prepareFreshStack(root);
}ReactFiber.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31/**
* 根据老fiber和新的属性构建新fiber
* @param {*} current 老fiber
* @param {*} pendingProps 新的属性
*/
export function creatWorkInProgress(current, pendingProps) {
// 3. 拿到老fiber的轮替 第一次没有 (初始化)
let workInProgress = current.alternate;
if(workInProgress === null) {
workInProgress = createFiberNode(current.tag, pendingProps, current.key);
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
workInProgress.stateNode = current;
current.alternate = workInProgress;
} else {
// 如果有,说明是更新,只能改属性就可以复用
workInProgress.pendingProps = current.pendingProps;
workInProgress.type = current.type;
workInProgress.flags = current.flags;
workInProgress.subtreeFlags = NoFlags;
}
// 复制属性
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
return workInProgress;
}
执行工作单元
然后在新的根 fiber 里构建更新 fiber 树
ReactFiberWorkLoop.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33import { beginWork } from "./ReactFiberBeginWork";
...
function renderRootSync(root) {
// 2. 先构建一个空的栈
prepareFreshStack(root);
// 1. 现在的 workInProgress 是新的根fiber
workLoopSync();
}
// 工作同步循环
function workLoopSync() {
while(workInProgress !== null) {
// 2. 执行工作单元
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if(next === null) {
// 说明已经完成
// 完成工作单元
// completeUnitOfWork(); // 这个方法之后写 先模拟一下完成工作
workInProgress = null;
} else {
// 如果有子节点就成为下一个工作单元
workInProgress = next;
}
}ReactFiberBeginWork.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38import { HostComponent, HostRoot, HostText } from "./ReactWorkTags";
import { processUpdateQueue } from './ReactFiberClassUpdateQueue';
/**
* 5. 根据 `新的` 虚拟dom去构建 `新的` fiber链表
* @param {*} current 老fiber
* @param {*} workInProgress 新fiber
* @returns 下一个工作单元
*/
export function beginWork(current, workInProgress) {
console.log("beginWork", workInProgress);
// 判断类型不同处理方式返回子节点或者弟弟
switch(workInProgress.tag) {
case HostRoot:
return updateHostRoot(current, workInProgress);
case HostComponent:
return updateHostComponent(current, workInProgress);
case HostText:
return null;
default:
return null;
}
}
function updateHostRoot(current, workInProgress) {
// 需要知道它的子虚拟dom, 知道它的儿子的虚拟dom信息
// 之前在根fiber的更新队列加的虚拟dom, 可以在这获取
processUpdateQueue(workInProgress);
const nextState = workInProgress.memoizedState;
// 获取虚拟节点
const nextChildren = nextState.element;
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
function updateHostComponents(current, workInProgress) {}
获取更新队列的虚拟 dom
写上一步引入的 processUpdateQueue
方法
- ReactFiberClassUpdateQueue.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89import { markUpdateLaneFromFiberToRoot } from './ReactFiberConcurrentUpdate'
export function initialUpdateQueue(fiber) {
// 创建一个更新队列
// pending 是循环链表
const queue = {
shared: {
pending: null,
}
}
fiber.updateQueue = queue;
}
// 更新状态
export const UpdateState = 0;
export function createUpdate() {
const update = {tag: UpdateState};
return update;
}
export function enqueueUpdate(fiber, update) {
// 获取根fiber的更新队列 (上一篇最后加的)
const updateQueue = fiber.updateQueue;
// 获取等待执行的任务
const pending = updateQueue.shared.pending;
// 说明初始化的状态
if(pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
// 让等待更新指向当前update 开始更新
updateQueue.shared.pending = update;
// 从当前的fiber 到返回找到并返回根节点
return markUpdateLaneFromFiberToRoot(fiber);
}
/**
* 根据老状态和更新队列的更新计算最新的状态
* @param {*} workInProgress 要计算的fiber
*/
export function processUpdateQueue(workInProgress) {
// 拿到更新队列
const queue = workInProgress.updateQueue;
// 等待生效的队列
const pendingQueue = queue.shared.pending;
// 如果有更新, 或者更新队列里有内容
if(pendingQueue !== null) {
// 清除等待生效的更新 因为在这就要使用了可以清除了
queue.shared.pending = null;
// 获取最后一个等待生效的更新
const lastPendingUpdate = pendingQueue;
// 第一个等待生效的更新
const firstPendingUpdate = pendingQueue.next;
// 把更新链表剪开, 变成单向链表
lastPendingUpdate.next = null;
// 获取老状态 (会不停更新和计算赋值新状态, 所以起名newState)
let newState = workInProgress.memoizedState;
let update = firstPendingUpdate;
while(update) {
// 根据老状态和更新计算新状态
newState = getStateFromUpdate(update, newState);
update = update.next;
}
// 把最终计算到的状态赋值给 memoizedState
workInProgress.memoizedState = newState;
}
}
/**
* 根据老状态和更新, 计算新状态
* @param {*} update 更新
* @param {*} prevState 上一个状态
* @returns 新状态
*/
function getStateFromUpdate(update, prevState) {
switch(update.tag) {
case UpdateState:
const { payload } = update;
return assign({}, prevState, payload);
}
}
根据子虚拟 dom 创建子 fiber 节点
上上步还有一个 reconcileChildren
没有定义
ReactFiberBeginWork.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import { mountChildFibers, reconcileChildFibers } from "./ReactChildFiber";
/**
* 根据新的虚拟dom生成新的fiber链表
* @param {*} current 老的父fiber
* @param {*} workInProgress 新的父fiber
* @param {*} nextChildren 新的子虚拟dom
*/
function reconcileChildren(current, workInProgress, nextChildren) {
// 如果此新fiber没有老fiber, 说明是新创建的
if (current === null) {
// 挂在子fiber
workInProgress.child = mountChildFibers(workInProgress, null, next);
} else {
// 更新: 协调子fiber列表 需要做DOM-DIFF (初始化时的根fiber是有老fiber的(一开始创建的))
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren
);
}
}ReactChildFiber.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45import { createFiberFromElement } from './ReactFiber';
import { REACT_ELEMENT_TYPE } from '../../shared/ReactSymbols';
/**
*
* @param {*} shouldTrackSideEffect 是否跟踪副作用
* @returns
*/
function createChildReconciler(shouldTrackSideEffect) {
function reconcileSingElement(returnFiber, currentFirstFiber, element) {
// 因为我们实现的是初次挂载, 老节点currentFirstFiber是没有的,
// 所以可以直接根据虚拟dom创建fiber节点
const created = createFiberFromElement(element);
created.return = returnFiber;
return created;
}
/**
* 比较子fiber (DOM-DIFF) 就是用老的fiber链表和新的虚拟dom进行比较
* @param {*} returnFiber 新父fiber
* @param {*} currentFirstFiber 当前的第一个子fiber(老fiber的第一个儿子)
* @param {*} newChild 新的子虚拟dom
*/
function reconcileChildFibers(returnFiber, currentFirstFiber, newChild) {
// 现在暂时只考虑新的节点只有一个的情况
if(typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return reconcileSingElement(
returnFiber,
currentFirstFiber,
newChild
);
default:
break;
}
}
}
return reconcileChildFibers;
}
// 有老父fiber 更新
export const reconcileChildFibers = createChildReconciler(true);
// 没有老的父fiber 更新
export const mountChildFibers = createChildReconciler(false);ReactFiber.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19...
export function createFiberFromElement(element) {
const type = element.type;
const key = element.key;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(
type,
key,
pendingProps
);
return fiber;
}
export function createFiberFromTypeAndProps(type, key, pendingProps) {
let fiberTag = IndeterminateComponent;
const fiber = createFiberNode(fiberTag, pendingProps, key);
fiber.type = type;
return fiber;
}
完成工作单元
- ReactFiberWorkLoop.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36function completeUnitOfWork(unitOfWork) {
...
if (next === null) {
// 说明已经完成
// 完成工作单元
+ completeUnitOfWork(unitOfWork); // 这个方法之后写 先模拟一下完成工作
// workInProgress = null;
} else {
// 如果有子节点就成为下一个工作单元
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completeWork = unitOfWork;
do {
// 拿到他的父节点和当前节点RootFiber
const current = completeWork.alternate;
const returnFiber = completeWork.return;
let next = completeWork(current, completeWork);
// 如果下一个节点不为空
if(next !== null) {
workInProgress = next;
return;
}
const siblingFiber = completeWork.sibling;
// 如果兄弟节点不为空
if(siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
// 返回父节点
completeWork = returnFiber;
} while(completeWork !== null);
}