相对完整的梳理一下ReactDOM.render的大致流程

更新


[2019-7-17]

  • Initial release

前言


前几天已经简单总结过了ReactDOM.render的简单流程(#1).

但是没有进行深入的理解, 对于内部机制, 比如:

  • reconcile
  • schedule

没有过多的去解读, 毕竟错综复杂的源码加上FB那帮人的神奇脑洞.

让整个react源码变得晦涩难懂.

所以决定新开一个issue, 对照源码, 将ReactDOM.render的流程再梳理一遍.

必备


react体系中有几个常见的对象

  • DOMContainer
  • ReactRoot
  • FiberRoot
  • RootFiber
  • Fiber

至于它们之间的关系, 我之前有画过简单的xmind脑图:

https://github.com/ddzy/react-reading-sources/blob/master/xmind/1-react%E5%90%84%E5%A4%A7%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB.xmind

调和(reconcile)


节点准备

通过一系列的内部函数调用, 分别创建ReactRootFiberRootRootFiber等多个内部节点

  • ReactDOM.render

应用入口

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const ReactDOM = {
render(
element,
container,
) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}
};
  • legacyRenderSubtreeIntoContainer

创建react应用根节点 —— ReactRoot, 并且调用ReactRoot.render开始渲染

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
let root: Root = (container._reactRootContainer: any);

if (!root) {
// 创建React应用根节点
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
// 首次渲染, 不采用批量更新
unbatchedUpdates(() => {
root.render(children, callback);
});
}
}
  • ReactRoot

react根节点构造函数. 内部会创建FiberRoot

源码
1
2
3
4
5
6
7
8
9
10
11
function ReactRoot(
// DOM根节点
container: DOMContainer,
// 是否启用ConcurrentMode, 也就是任务优先级、任务切片, 默认为false
isConcurrent: boolean,
) {
// 创建FiberRoot
// 记录整个应用更新过程中的各种信息
const root = createContainer(container, isConcurrent, hydrate);
this._internalRoot = root;
}
  • createContainer

依次调用createFiberRootcreateHostRootFiber, 来创建并返回一个新的FiberRoot对象

源码
1
2
3
4
5
6
function createContainer(
containerInfo: DOMContainer,
isConcurrent: boolean = false,
) {
return createFiberRoot(containerInfo, isConcurrent);
}
源码
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
function createFiberRoot(
containerInfo: DOMContainer,
isConcurrent: boolean = false,
) {
// 创建RootFiber => HostRoot
const uninitializedFiber = createHostRootFiber(isConcurrent);

return {
// 对应的DOMContainer
containerInfo: any,

// Used only by persistent updates.
pendingChildren: any,

// FiberRoot => 整个`Fiber树`的顶点 => HostRootFiber
current: Fiber,

// 暂停提交的最早和最新优先级
earliestSuspendedTime: ExpirationTime,
latestSuspendedTime: ExpirationTime,

// 不确定是否暂停的最新和最老的优先级
earliestPendingTime: ExpirationTime,
latestPendingTime: ExpirationTime,

// promise执行resolve之后的最新优先级
latestPingedTime: ExpirationTime,

pingCache:
| WeakMap<Thenable, Set<ExpirationTime>>
| Map<Thenable, Set<ExpirationTime>>
| null,

// 如果出现错误, 并且队列中没有更多的更新
// 在处理错误之前, 会重新从根开始同步渲染
didError: boolean,

pendingCommitExpirationTime: ExpirationTime,
// A finished work-in-progress HostRoot that's ready to be committed.

// 完成调度&更新的RootFiber.alternate
finishedWork: Fiber | null,

// Timeout handle returned by setTimeout. Used to cancel a pending timeout, if
// it's superseded by a new one.
timeoutHandle: TimeoutHandle | NoTimeout,
// Top context object, used by renderSubtreeIntoContainer
context: Object | null,
pendingContext: Object | null,
// Determines if we should attempt to hydrate on the initial mount
+hydrate: boolean,

// 当前RootFiber剩余的更新时间
nextExpirationTimeToWorkOn: ExpirationTime,

// 更新过期时间
expirationTime: ExpirationTime,

// List of top-level batches. This list indicates whether a commit should be
// deferred. Also contains completion callbacks.
// TODO: Lift this into the renderer
firstBatch: Batch | null,

// FiberRoot为单向链表结构
// 防止有多个root, 也就是调用多次ReactDOM.render
nextScheduledRoot: FiberRoot | null,
};
}

节点更新

创建好了基本的节点, 开始更新

又回到了legacyRenderSubtreeIntoContainer这个方法

其内部调用了ReactRoot.render(children)来进行首次更新.

  • ReactRoot.prototype.render
源码
1
2
3
4
5
6
7
8
9
10
11
ReactRoot.prototype.render = function(
children: ReactNodeList,
callback: ?() => mixed,
): Work {
const root = this._internalRoot;

// 与`createContainer`相比
// 前者创建`FiberRoot`
// 后者更新`FiberRoot`
updateContainer(children, root);
};
  • updateContainer

更新RootFiber

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
){

// 获取RootFiber => HostRoot
const current = container.current;

// 是`动态`的, 表示页面从js开始加载到当前的时间戳, 也有可能是上一个fiber的`currentSchedulerTime`, 这
是为了让批量更新具有相同的expirationTime, 避免多次更新.
const currentTime = requestCurrentTime();

// `concurrent`根据其来进行优先级更新调度, 值越小, 优先级越高
const expirationTime = computeExpirationForFiber(currentTime, current);

return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}

由于requestCurrentTimecomputeExpirationTimeForFiber非常重要, 所以还是mark一下

先来看一下requestCurrentTime, 它的作用是: 计算从页面开始加载到当前的时间戳

也有可能是另外几种情况, 会在下面的源码中进行注释:

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function requestCurrentTime() {
if (isRendering) {
// 已经开始渲染了, 为了避免重复更新, 所以返回上一次的调度时间
// currentRendererTime >= currentSchedulerTime
return currentSchedulerTime;
}

findHighestPriorityRoot();
if (
nextFlushedExpirationTime === NoWork ||
nextFlushedExpirationTime === Never
) {
// 如果没有等待处理的任务
// 更新渲染时间, 也就是`currentRendererTime`
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;

return currentSchedulerTime;
}

// 如果有被挂起的, 也就是等待处理的任务
// 直接返回该任务的currentSchedulerTime
return currentSchedulerTime;
}

看到这里, requestCurrentTime利用currentSchedulerTime这个变量

巧妙地让批量更新(batchedUpdates)具有相同的expirationTime, 避免了expirationTime的不同导致进行多次更新, 影响性能

话不多说, 接着来看computeExpirationTimeForFiber这个方法, 其内部对5种不同类型的expirationTime进行了不同的处理:

源码
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
function computeExpirationForFiber(
// 经由`requestCurrentTime`计算出来的值
currentTime: ExpirationTime,
// fiber对象
fiber: Fiber
) {
let expirationTime;

if (expirationContext !== NoWork) {

} else if (isWorking) {
if (isCommitting) {
// 在commit提交阶段产生的更新, 应该是同步(Sync)的expirationTime
// ①: Sync
expirationTime = Sync;
} else {
// render渲染阶段产生的更新, 应该与上一次具有相同的expirationTime
expirationTime = nextRenderExpirationTime;
}
} else {
// 如果没有正在进行的工作, 也就是不处于`isWorking`状态

// 根据`isConcurrentMode`来判断是否开启异步渲染模式
if (fiber.mode & ConcurrentMode) {
if (isBatchingInteractiveUpdates) {
// 如果是交互式(Interactive)更新, react的事件系统
// ②: Interactive
expirationTime = computeInteractiveExpiration(currentTime);
} else {
// 如果是普通的异步(Sync)更新, 普通的setState
// ③: Async
expirationTime = computeAsyncExpiration(currentTime);
}
} else {
// 同步渲染
// 因为在`legacyCreateRootFromDOMContainer`函数内部, 默认设置了`isConcurrent`为false
// ④: NoWork
expirationTime = Sync;
}
}

return expirationTime;
}
  • updateContainerAtExpirationTime

  • scheduleRootUpdate

reconcile -> schedule的桥梁

在其内部进行创建更新(update), 更新入队…等一系列操作

之后进入schedule阶段, 嘀嘀嘀

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function scheduleRootUpdate(
// RootFiber
current: Fiber,
// ReactDOM.render的第一个参数
element: ReactNodeList,
expirationTime: ExpirationTime,
) {
// 创建更新
const update = createUpdate(expirationTime);

// 更新参数
update.payload = {element};
update.callback = callback;

// 更新入队
// 将更新追加到对应fiber的`updateQueue`上
enqueueUpdate(current, update);

// 进入调度(schedule)阶段
scheduleWork(current, expirationTime);
}

————————————–简简单单的分割线————————————————–

调度(schedule)


经过了上面的调和(reconcile)阶段, 已经做好了基本的准备工作:

  • 计算expirationTime
  • update的创建、初始化、入队
  • 其它的杂七杂八的操作

而在调和阶段的最后, 有一段这样的代码:

源码
1
scheduleWork(current, expirationTime);

标志着, 整个应用开始进入最核心的调度(schedule)阶段

scheduleWork

调度主入口. ReactDOM.renderClassComponentInstance.setState… 都会进行调用该方法:

源码
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
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
// 寻找`FiberRoot`, 更新`expirationTime`
const root = scheduleWorkToRoot(fiber, expirationTime);

// ! [MARK]: nextRenderExpirationTime -> 当前正在调度工作的过期时间
// ! [MARK]: expirationTime -> 新产生的任务的过期时间
// ! [MARK]: childExpirationTime -> 快速确定子fiber有没有挂起的等待执行的任务

// 此次判断就是fiber的特性之一 --> 任务优先级机制
if (
!isWorking &&
nextRenderExpirationTime !== NoWork &&
expirationTime > nextRenderExpirationTime
) {
// 新的任务(ReactDOM.render)优先级较高, 终止当前任务(首次渲染时产生的更新)
// nextUnitOfWork - 表示当前正在调度的fiber
// 重置当前调度的任务为null
resetStack();
}

// 标志优先级
markPendingPriorityLevel(root, expirationTime);

if (
// If we're in the render phase, we don't need to schedule this root
// for an update, because we'll do it before we exit...
!isWorking ||
isCommitting ||
// ...unless this is a different root than the one we're rendering.
nextRoot !== root
) {
const rootExpirationTime = root.expirationTime;
// 开始进入请求工作
// react应用产生的所有更新, 都交由FiberRoot来处理.
requestWork(root, rootExpirationTime);
}
}

requestWork

请求工作:

源码
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
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
// 添加FiberRoot到调度队列
addRootToSchedule(root, expirationTime);

if (isRendering) {
// Prevent reentrancy. Remaining work will be scheduled at the end of
// the currently rendering batch.
return;
}

/**
* ! 注意点:
* ! 1). 批次更新
* ! 2). react的事件系统会自动设置`isBatchingUpdates`的值为true
*/
if (isBatchingUpdates) {
// 在当前批次渲染的最后, 直接进行同步更新
if (isUnbatchingUpdates) {
// ...unless we're inside unbatchedUpdates, in which case we should
// flush it now.
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
performWorkOnRoot(root, Sync, false);
}
return;
}

/**
* ! 注意点:
* ! 1). 同步任务直接执行, 不能被打断
* ! 2). 异步任务, requestIdleCallback
*/
if (expirationTime === Sync) {
// 执行同步任务
performSyncWork();
} else {
// 在浏览器空闲时间, 执行异步任务
scheduleCallbackWithExpirationTime(root, expirationTime);
}
}

———————————-有空再更———————————–

接着更新, 上面在requestWork中, 根据expirationTime是否同步or异步, 来进行不同的performWork, performWork内部机制是什么? 继续看源码.

performSyncWork

关于performSyncWork较简单, 由于属于同步任务, 所以直接省去requestIdleCallback这个环节, 直接调用performWork:

源码
1
2
3
function performSyncWork() {
performWork(Sync);
}

scheduleCallbackWithExpirationTime

scheduleCallbackWithExpirationTime主要的功能是:

  • 计算timeout, 也就是requestIdleCallbacktimeout`参数

先来列出几个重要的全局变量:

  • callbackExpirationTime 正在分片的任务的expirationTime
  • callbackID 正在分片的任务的id, 功能类似于setTimeoutId, 但是结构不同, react自行定义了它的结构.

接着看一下源码:

源码
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
function scheduleCallbackWithExpirationTime(
root: FiberRoot,
expirationTime: ExpirationTime,
) {
// 如果当前已经有任务在分片了 - 任务优先级机制
if (callbackExpirationTime !== NoWork) {
// 如果新的任务优先级 < 当前正在进行的任务
if (expirationTime < callbackExpirationTime) {
return;
} else {
// 如果新的任务优先级 > 当前正在进行的任务
if (callbackID !== null) {
// 强制退出当前分片的任务
cancelCallback(callbackID);
}
}
}

/**
* ! 注意点:
* ! 1). 浏览器正常的刷新频率为30HZ
* ! 2). 等同于在`一秒`内需要刷新30次
* ! 3). 每一帧的时间为`33ms`
* ! 4). 在`33ms`内需要做js执行、动画、重绘重排等操作
*/

// 保存即将进行分片的任务
callbackExpirationTime = expirationTime;
const currentMs = now() - originalStartTimeMs;
const expirationTimeMs = expirationTimeToMs(expirationTime);

// timeout = 组件过期的时间 - 当前时间 = requestIdleCallback的timeout参数
const timeout = expirationTimeMs - currentMs;

// 模拟`requestIdleCallback`API
// react通过`scheduleCallback`来实现任务分片机制
// 在一个个任务分片中, 实现任务优先级
// 将传递的`performAsyncWork`参数加入到双向循环链表.
callbackID = scheduleCallback(performAsyncWork, {timeout});
}

performAsyncWork

react通过模拟requestIdleCallbackAPI, 使得浏览器可以在每一帧的空闲时间来执行react代码. react维护了一个callback双向循环链表. 因此, 浏览器可以在空闲时间遍历改链表, 进而实现任务的分片机制.

关于scheduleCallback是如何实现的? 或者react是如何模拟requestIdleCallback的? 后面再开新的issue记录吧.

源码
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
function performAsyncWork(didTimeout) {
// 如果FiberRoot的expirationTime超时
if (didTimeout) {
if (firstScheduledRoot !== null) {
// 更新FiberRoot单向循环链表, 更新所有已经超时的expirationTime
// 为了安排它们在一个批次进行批量更新
recomputeCurrentRendererTime();
let root: FiberRoot = firstScheduledRoot;
do {
didExpireAtExpirationTime(root, currentRendererTime);
// The root schedule is circular, so this is never null.
root = (root.nextScheduledRoot: any);
} while (root !== firstScheduledRoot);
}
}

findHighestPriorityRoot();

if (disableYielding) {
while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork) {
performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
findHighestPriorityRoot();
}
}

// If we're inside a callback, set this to false since we just completed it.
callbackExpirationTime = NoWork;
callbackID = null;

// 如果在本次时间分片内没有执行完任务
// 重新安排
if (nextFlushedExpirationTime !== NoWork) {
scheduleCallbackWithExpirationTime(
((nextFlushedRoot: any): FiberRoot),
nextFlushedExpirationTime,
);
}

// Clean-up.
finishRendering();
}

performWorkOnRoot

先梳理一下同/异步任务的走向:

源码
1
2
3
4
5
// 同步任务
performSyncWork -> performWorkSync -> performWork -> performWorkOnRoot

// 异步任务
scheduleCallbackWithExpirationTime -> scheduleCallback -> performAsyncWork -> performWorkOnRoot

接着简单看一下performOnRoot, 此时进入isRendering阶段. 它包括两个部分:

  • 如果root.finishWork为null, 则进入isCommitting阶段(completeRoot)
  • 反之, 进入isWorking阶段(renderRoot)
源码
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
function performWorkOnRoot(
root: FiberRoot,
expirationTime: ExpirationTime,
isYieldy: boolean,
) {
isRendering = true;

if (!isYieldy) {
// ! 同步任务
// ! yield --> 等待, 表示不能暂停、被打断的任务

let finishedWork = root.finishedWork;
if (finishedWork !== null) {
completeRoot(root, finishedWork, expirationTime);
} else {
renderRoot(root, isYieldy);
finishedWork = root.finishedWork;
if (finishedWork !== null) {
// We've completed the root. Commit it.
completeRoot(root, finishedWork, expirationTime);
}
}
} else {
// ! 异步任务

let finishedWork = root.finishedWork;
if (finishedWork !== null) {
// This root is already complete. We can commit it.
completeRoot(root, finishedWork, expirationTime);
} else {
renderRoot(root, isYieldy);
completeRoot(root, finishedWork, expirationTime);
}
}

isRendering = false;
}

renderRoot

开始真正的渲染工作

源码
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
function renderRoot(root: FiberRoot, isYieldy: boolean): void {
isWorking = true;
const previousDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = ContextOnlyDispatcher;

const expirationTime = root.nextExpirationTimeToWorkOn;

// ! 凡是带有`next`字样的, 都代表当前正在进行的工作, 反之则代表新的工作
if (
expirationTime !== nextRenderExpirationTime ||
root !== nextRoot ||
nextUnitOfWork === null
) {
// 创建WorkInProgress-Fiber-Tree
nextUnitOfWork = createWorkInProgress(
nextRoot.current,
null,
nextRenderExpirationTime,
);
root.pendingCommitExpirationTime = NoWork;
}

// 是否出现致命错误
let didFatal = false;

do {
try {
// 大循环, 遍历整颗WorkInProgress树
workLoop(isYieldy);
} catch (thrownValue) {
// 如果出现错误, 对应的错误处理机制
if (nextUnitOfWork === null) {
// This is a fatal error.
didFatal = true;
onUncaughtError(thrownValue);
} else {
const sourceFiber: Fiber = nextUnitOfWork;
let returnFiber = sourceFiber.return;
if (returnFiber === null) {
// This is the root. The root could capture its own errors. However,
// we don't know if it errors before or after we pushed the host
// context. This information is needed to avoid a stack mismatch.
// Because we're not sure, treat this as a fatal error. We could track
// which phase it fails in, but doesn't seem worth it. At least
// for now.
didFatal = true;
onUncaughtError(thrownValue);
} else {
throwException(
root,
returnFiber,
sourceFiber,
thrownValue,
nextRenderExpirationTime,
);
nextUnitOfWork = completeUnitOfWork(sourceFiber);
continue;
}
}
}
break;
} while (true);

// Yield back to main thread.
// 如果渲染出现致命错误, 会退出并重新安排渲染
if (didFatal) {
onFatal(root);
return;
}

// We completed the whole tree.
const rootWorkInProgress = root.current.alternate;

// Ready to commit.
// 进入commit阶段
onComplete(root, rootWorkInProgress, expirationTime);
}

渲染(render)


此时, 从RootFiberWorkInProgress的开始, 逐步遍历整个Fiber树, 进入渲染阶段

WorkLoop

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function workLoop(isYieldy) {
if (!isYieldy) {
// 同步任务
// 不能被打断
// nextUnitOfWork === currentFiber.child, 也就是下一次进行渲染的fiber
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// 异步任务
// 可以被优先级更高的打断
while (nextUnitOfWork !== null && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}

performUnitOfWork

对单个Fiber节点进行更新. 同时更新fiber.stateNode, 也就是ReactElement.

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
const current = workInProgress.alternate;

// next === fiber.return
// 下一个渲染的fiber节点
let next;

next = beginWork(current, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;

ReactCurrentOwner.current = null;

return next;
}

beginWork

开始真正的渲染, 此时会从根据当前fibertag属性, 来对不同的组件做对应的处理. 关于fiber.tag有哪些具体属性, 可以参考这篇笔记:

fiber.tag类型汇总

接着看一下源码:

源码
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const updateExpirationTime = workInProgress.expirationTime;

if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;

if (oldProps !== newProps || hasLegacyContextChanged()) {
// If props or context changed, mark the fiber as having performed work.
// This may be unset if the props are determined to be equal later (memo).

// ! 根据`oldProps === newProps`判断是否已更新
didReceiveUpdate = true;
} else if (updateExpirationTime < renderExpirationTime) {
// ! 如果当前fiber节点的子树产生更新的优先级小于当前更新

didReceiveUpdate = false;
// This fiber does not have any pending work. Bailout without entering
// the begin phase. There's still some bookkeeping we that needs to be done
// in this optimized path, mostly pushing stuff onto the stack.
switch (workInProgress.tag) {
case HostRoot:
pushHostRootContext(workInProgress);
resetHydrationState();
break;

// ! HostComponent -> 代表原生DOM元素的字符串(div、p)
case HostComponent:
pushHostContext(workInProgress);
break;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
pushLegacyContextProvider(workInProgress);
}
break;
}
case HostPortal:
pushHostContainer(
workInProgress,
workInProgress.stateNode.containerInfo,
);
break;
case ContextProvider: {
const newValue = workInProgress.memoizedProps.value;
pushProvider(workInProgress, newValue);
break;
}
case Profiler:
if (enableProfilerTimer) {
workInProgress.effectTag |= Update;
}
break;
case SuspenseComponent: {
const state: SuspenseState | null = workInProgress.memoizedState;
const didTimeout = state !== null;
if (didTimeout) {
// If this boundary is currently timed out, we need to decide
// whether to retry the primary children, or to skip over it and
// go straight to the fallback. Check the priority of the primary
// child fragment.
const primaryChildFragment: Fiber = (workInProgress.child: any);
const primaryChildExpirationTime =
primaryChildFragment.childExpirationTime;
if (
primaryChildExpirationTime !== NoWork &&
primaryChildExpirationTime >= renderExpirationTime
) {
// The primary children have pending work. Use the normal path
// to attempt to render the primary children again.
return updateSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
} else {
// The primary children do not have pending work with sufficient
// priority. Bailout.
const child = bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
if (child !== null) {
// The fallback children have pending work. Skip over the
// primary children and work on the fallback.
return child.sibling;
} else {
return null;
}
}
}
break;
}
case DehydratedSuspenseComponent: {
if (enableSuspenseServerRenderer) {
// We know that this component will suspend again because if it has
// been unsuspended it has committed as a regular Suspense component.
// If it needs to be retried, it should have work scheduled on it.
workInProgress.effectTag |= DidCapture;
break;
}
}
}
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
} else {
didReceiveUpdate = false;
}

// Before entering the begin phase, clear the expiration time.
workInProgress.expirationTime = NoWork;

switch (workInProgress.tag) {
case IndeterminateComponent: {
const elementType = workInProgress.elementType;
return mountIndeterminateComponent(
current,
workInProgress,
elementType,
renderExpirationTime,
);
}
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
updateExpirationTime,
renderExpirationTime,
);
}
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderExpirationTime);
case HostComponent:
return updateHostComponent(current, workInProgress, renderExpirationTime);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
case HostPortal:
return updatePortalComponent(
current,
workInProgress,
renderExpirationTime,
);
case ForwardRef: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === type
? unresolvedProps
: resolveDefaultProps(type, unresolvedProps);
return updateForwardRef(
current,
workInProgress,
type,
resolvedProps,
renderExpirationTime,
);
}
case Fragment:
return updateFragment(current, workInProgress, renderExpirationTime);
case Mode:
return updateMode(current, workInProgress, renderExpirationTime);
case Profiler:
return updateProfiler(current, workInProgress, renderExpirationTime);
case ContextProvider:
return updateContextProvider(
current,
workInProgress,
renderExpirationTime,
);
case ContextConsumer:
return updateContextConsumer(
current,
workInProgress,
renderExpirationTime,
);
case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;

// Resolve outer props first, then resolve inner props.
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
resolvedProps = resolveDefaultProps(type.type, resolvedProps);

return updateMemoComponent(
current,
workInProgress,
type,
resolvedProps,
updateExpirationTime,
renderExpirationTime,
);
}
case SimpleMemoComponent: {
return updateSimpleMemoComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
updateExpirationTime,
renderExpirationTime,
);
}
case IncompleteClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return mountIncompleteClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case DehydratedSuspenseComponent: {
if (enableSuspenseServerRenderer) {
return updateDehydratedSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
}
break;
}
case EventComponent: {
if (enableEventAPI) {
return updateEventComponent(
current,
workInProgress,
renderExpirationTime,
);
}
break;
}
case EventTarget: {
if (enableEventAPI) {
return updateEventTarget(current, workInProgress, renderExpirationTime);
}
break;
}
}
}

组件更新

根据fiber.tag, 不同的组件进行不同的更新. 那么, 具体是如何更新的? 避免篇幅过长, 将其拆分为多篇笔记:

提交(commit)


待看

总结


[2019-7-15]

看到了beginWork中对于FunctionComponent的更新, 但是hook机制较为复杂, 需要花较长时间理解.

简单总结下, react中处理一个更新, 大致会经过下列流程:

  • 产生更新
  • 计算expirationTime
  • 创建更新update
  • 更新入队updateQueue
  • 寻找FiberRoot
  • FiberRoot加入单向循环链表
  • 遍历FiberRoot链表, 通过findHighestPriorityRoot寻找最高优先级的FiberRoot, 依次执行performWorkOnRoot
  • isRendering
  • 找到RootFiber, 创建WorkInProgress
  • isWorking
  • 进入WorkLoop大循环, 对WorkInProgress返回的每个fiber.child做处理
  • 进入beginWork阶段, 根据当前fiber的tag做不同的更新.
  • ……