React-render阶段(一)

概览

React16.8版本之后, 使用Fiber提供了任务的优先级,中断可恢复的能力(开启CM模式). React通过Scheduler将高优先级的任务率先扔进Reconciler, Reconcile阶段创建了每个节点(其中可能经历复用或者diff等),生成Fiber树,并生成对应的dom树(暂未插入到页面),这个阶段我们称之为render阶段(渲染阶段)

React Fiber使用了双缓存机制, 提供了两棵Fiber树, 当前展示的这棵树我们称之为current树(本次更新的上一次更新的树), 还有一棵是首次更新或者触发更新后在内存中生成的树, 我们称之为WorkInProgress

双缓存

current树: current树上的每一个工作单元展示了当前页面的dom情况, 首屏渲染时, current树只存在应用程序的根rootFiber节点

workInProgress树: workInProgress树上的每一个工作单元的形成会在内存中执行, 渲染mount或者update时的更新逻辑

具体可以参考卡老师写的React揭秘中理念篇的Fiber结构的工作原理,来详细的理解双缓存的概念——什么是双缓存

渲染阶段

render阶段开始于performSyncWorkOnRootperformConcurrentWorkOnRoot,取决于当前的模式. 这个函数是React整个流程,包括render阶段commit阶段

上图红框标出三个断点, 第一个断点是render阶段的核心

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 renderRootSync(root, lanes) {
// ......
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
{
if (isDevToolsPresent) {
var memoizedUpdaters = root.memoizedUpdaters;

if (memoizedUpdaters.size > 0) {
restorePendingUpdaters(root, workInProgressRootRenderLanes);
memoizedUpdaters.clear();
} // At this point, move Fibers that scheduled the upcoming work from the Map to the Set.
// If we bailout on this work, we'll move them back (like above).
// It's important to move them now in case the work spawns more work at the same priority with different updaters.
// That way we can keep the current update and future updates separate.


movePendingFibersToMemoized(root, lanes);
}
}
// 重置调度队列,并从root节点(新的高优先级的节点)开始调度
prepareFreshStack(root, lanes);
}

{
markRenderStarted(lanes);
}

do {
try {
// 尝试循环创建工作单元
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);

// .....
return workInProgressRootExitStatus;
} // Th

这个方法最重要的两个方法, 一个是prepareFreshStack, 这个方法是用于重置调度队列,并从root节点(新的高优先级的节点)开始调度, 这个函数内部调用了createWorkInProgress, 我们放到下面一会讲

1
2
3
4
5
6
7
8
9
10
11
12
13
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}

// performConcurrentWorkOnRoot会调用该方法
function workLoopConcurrent() {
//和上面的区别在于shouldYield, 这个代表是否存在剩余时间
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}

还有一个是workLoopSync, 循环创建调用performUnitOfWork, 通过函数名可以看到是循环执行工作单元, 为遍历到的每个Fiber节点提供beginWork

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

function performUnitOfWork(unitOfWork) {
var current = unitOfWork.alternate;
setCurrentFiber(unitOfWork);
var next;
if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
// 开始beginWork阶段, 主要是创建当前节点的子Fiber节点
next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
}

resetCurrentFiber();
unitOfWork.memoizedProps = unitOfWork.pendingProps;

if (next === null) {
// 如果子节点为null, 那么直接把当前节点completeWork
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}

ReactCurrentOwner$2.current = null;
}

beginWork的主要作用在于创建当前节点的子Fiber节点, 该方法为render阶段递归阶段阶段的主要方法

beginWork

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
function beginWork(current, workInProgress, renderLanes) {
var updateLanes = workInProgress.lanes;
{
if (workInProgress._debugNeedsRemount && current !== null) {
// This will restart the begin phase with a new fiber.
return remountFiber(current, workInProgress, createFiberFromTypeAndProps(workInProgress.type, workInProgress.key, workInProgress.pendingProps, workInProgress._debugOwner || null, workInProgress.mode, workInProgress.lanes));
}
}

// 当前的workInProgress树上正在工作的单元对应的alternate对应的Fiber节点
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;

if (oldProps !== newProps || hasContextChanged() || (
workInProgress.type !== current.type )) {
// props或者context变化, 有更新的逻辑
didReceiveUpdate = true;
} else if (!includesSomeLane(renderLanes, updateLanes)) {// 当前fiber上是否存在更新,如果存在那么更新的优先级是否和本次整棵fiber树调度的优先级一致
// 以下表示当前fiber上的更新与本颗fiber树上的更新不一致或者说当前fiber上不存在更新
didReceiveUpdate = false;

// 这里主要是针对不接受更新的ReactElement对象, 进行复用, 针对不同的类型传递一些属性
switch (workInProgress.tag) {
case HostRoot:
// ...
case // ...
}

return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else {
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// 这个解决了context改变的时候suspense没有触发更新的问题
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
}

workInProgress.lanes = NoLanes;

switch (workInProgress.tag) {
// 根据tag判断当前工作单元的类型, 基于当前类型进入不同的逻辑
case IndeterminateComponent:
// ...
case LazyComponent:
// ...

case FunctionComponent:
{
var _Component = workInProgress.type;
var unresolvedProps = workInProgress.pendingProps;
var resolvedProps = workInProgress.elementType === _Component ? unresolvedProps : resolveDefaultProps(_Component, unresolvedProps);
return updateFunctionComponent(current, workInProgress, _Component, resolvedProps, renderLanes);
}

case ClassComponent:
{
var _Component2 = workInProgress.type;
var _unresolvedProps = workInProgress.pendingProps;

var _resolvedProps = workInProgress.elementType === _Component2 ? _unresolvedProps : resolveDefaultProps(_Component2, _unresolvedProps);

return updateClassComponent(current, workInProgress, _Component2, _resolvedProps, renderLanes);
}

case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);

case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);

case xxxx:
// ..........
}
}

我们可以看到beginWork主要做了几件事情

  1. 根据current判断这个工作单元处于挂载还是更新, 根据新旧propscontext判断能否复用
  2. 如果不能复用则更新, 根据当前工作单元的类型决定进入什么逻辑
    • 工作单元类型可以在src/react-reconciler/ReactWorkTags.js中找到, div属于HostComponent

beginWork中的更新逻辑(以div举例)

如果当前工作单元是div, 那么会进入updateHostComponent的更新逻辑

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 updateHostComponent(current, workInProgress, renderLanes) {
pushHostContext(workInProgress);

if (current === null) {
tryToClaimNextHydratableInstance(workInProgress);
}

var type = workInProgress.type;
var nextProps = workInProgress.pendingProps;
var prevProps = current !== null ? current.memoizedProps : null;
var nextChildren = nextProps.children;
var isDirectTextChild = shouldSetTextContent(type, nextProps);
// 文本节点的优化, 当div下只有一个单独的文本节点,那么react不会单独为这个Fiber节点创建子节点
if (isDirectTextChild) {
nextChildren = null;
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
workInProgress.flags |= ContentReset;
}

markRef(current, workInProgress);
// reconcileChildren是diff子节点, 如果current是null, 说明是mount, 否则是update
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

针对div的更新, 主要调用了reconcileChilren的方法, 以下是reconcileChilren方法

1
2
3
4
5
6
7
8
9
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
if (current === null) {
// mount时
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
} else {
// update时
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}
}

reconcileChilrenmount时调用了mountChildFibers, 在update时调用了reconcileChildFibers, 这两个方法都是调用的ChildReconciler, 返回一个节点并赋值给workInProgress.child, 因此可以验证beginWork的目的是创建当前节点的子Fiber 节点

1
2
var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);

ChildReconciler的参数的含义代表的是是否需要追踪副作用, mount时不需要追踪副作用,原因是我们只需要被插入一次, 如果追踪副作用, 那么每个节点都将被打上effectTagPlacement(插入), 这样commit阶段所有节点都会被插入一次, 这种频繁操作dom的行为显然是消耗性能且没有必要的

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
function ChildReconciler(shouldTrackSideEffects) {
// ......其他节点的diff算法
// 这里只展示单一节点的diff算法的源码
function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
var key = element.key;
var child = currentFirstChild;

while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
var elementType = element.type;

if (elementType === REACT_FRAGMENT_TYPE) {
if (child.tag === Fragment) {
deleteRemainingChildren(returnFiber, child.sibling);
var existing = useFiber(child, element.props.children);
existing.return = returnFiber;

{
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}

return existing;
}
} else {
if (child.elementType === elementType || ( // Keep this check inline so it only runs on the false path:
isCompatibleFamilyForHotReloading(child, element) ) || // Lazy types should reconcile their resolved type.
// We need to do this after the Hot Reloading check above,
// because hot reloading has different semantics than prod because
// it doesn't resuspend. So we can't let the call below suspend.
typeof elementType === 'object' && elementType !== null && elementType.$$typeof === REACT_LAZY_TYPE && resolveLazy(elementType) === child.type) {
deleteRemainingChildren(returnFiber, child.sibling);

var _existing = useFiber(child, element.props);

_existing.ref = coerceRef(returnFiber, child, element);
_existing.return = returnFiber;

{
_existing._debugSource = element._source;
_existing._debugOwner = element._owner;
}

return _existing;
}
} // Didn't match.


deleteRemainingChildren(returnFiber, child);
break;
} else {
deleteChild(returnFiber, child);
}

child = child.sibling;
}

if (element.type === REACT_FRAGMENT_TYPE) {
var created = createFiberFromFragment(element.props.children, returnFiber.mode, lanes, element.key);
created.return = returnFiber;
return created;
} else {
var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);

_created4.ref = coerceRef(returnFiber, currentFirstChild, element);
_created4.return = returnFiber;
return _created4;
}
}

function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
var isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null;

if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
} // Handle object types
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));

case REACT_PORTAL_TYPE:
return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes));

case REACT_LAZY_TYPE:
{
var payload = newChild._payload;
var init = newChild._init; // TODO: This function is supposed to be non-recursive.

return reconcileChildFibers(returnFiber, currentFirstChild, init(payload), lanes);
}

}
}
// return deleteRemainingChildren(returnFiber, currentFirstChild);
}

return reconcileChildFibers;
}

手动执行ChildReconciler会返回reconcileChildFibers, 这里巧妙的使用了闭包, 根据newChild中的$$typeof去进行不同逻辑.
参数的newChild可以看到向上追溯看到是当前workInProgress.pendingProps.children, 也就是当前工作单元props上的children
reconcileSingleElement代码已经展示在上面, 是单一节点的diff算法, 等讲到diff算法时在详细解读

1
2
3
4
5
6
7
8
9
function placeSingleChild(newFiber) {
// This is simpler for the single child case. We only need to do a
// placement for inserting new children.
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags |= Placement;
}

return newFiber;
}

placeSingleChild就是为新节点添加Placement操作(mount时不添加), 并将其返回并添加到workInProgress.child

以上为beginWork的完整流程, 目的在于创建当前工作单元的第一个子Fiber节点


接下来补充一点逻辑, 为了方便调试, 并解决一些个人遇到的疑难困惑点

bailoutOnAlreadyFinishedWork

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes) {
if (current !== null) {
workInProgress.dependencies = current.dependencies;
}
{
stopProfilerTimerIfRunning();
}
markSkippedUpdateLanes(workInProgress.lanes); // Check if the children have any pending work.

if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
{
return null;
}
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}

这个函数的作用在于复用Fiber节点, 函数调用的时机仅仅会发生在update时, 根据双缓存机制, 当更新时, current树已经存在对应的Fiber节点, 在上文beginWork中有以下这么一段代码, 我们可以看到当current树不为null并且新旧Props contexttype无变化的时候, 会复用节点(即return bailoutOnAlreadyFinishedWork),bailoutOnAlreadyFinishedWork调用cloneChildFibers

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
// 当前的workInProgress树上正在工作的单元对应的alternate对应的Fiber节点
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;

if (oldProps !== newProps || hasContextChanged() || (
workInProgress.type !== current.type )) {
// props或者context变化, 有更新的逻辑
didReceiveUpdate = true;
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false;

// 这里主要是针对不接受更新的ReactElement对象, 进行复用, 针对不同的类型传递一些属性
switch (workInProgress.tag) {
case HostRoot:
// ...
case // ...
}

return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else {
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// 这个解决了context改变的时候suspense没有触发更新的问题
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
}

cloneChildFibers

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 cloneChildFibers(current, workInProgress) {
if (!(current === null || workInProgress.child === current.child)) {
{
throw Error( "Resuming work not yet implemented." );
}
}

if (workInProgress.child === null) {
return;
}

var currentChild = workInProgress.child;
var newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
workInProgress.child = newChild;
newChild.return = workInProgress;

while (currentChild.sibling !== null) {
currentChild = currentChild.sibling;
newChild = newChild.sibling = createWorkInProgress(currentChild, currentChild.pendingProps);
newChild.return = workInProgress;
}

newChild.sibling = null;
}

从名称可以读出, 这个函数的作用就是clone当前工作单元的子Fiber节点, 那么复用节点目的就是把current树上对应的现在Fiber节点的子Fiber节点保存到当前工作单元的child属性下, 这个方法会调用createWorkInProgress, 所以调用createWorkInProgress目的创建的不是当前工作单元, 而是当前工作单元的子工作单元

createWorkInProgress

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
function createWorkInProgress(current, pendingProps) {
var workInProgress = current.alternate;

if (workInProgress === null) {
// 如果workInProgress是null, 那么为其创建一个Fiber节点
workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;

// ...省略部分赋值

// 连接两课Fiber树
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps; // Needed because Blocks store data on type.

workInProgress.type = current.type; // We already have an alternate.
// Reset the effect tag.

workInProgress.flags = NoFlags; // The effects are no longer valid.

workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;

// ...省略部分代码

}

workInProgress.flags = current.flags & StaticMask;
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue; // Clone the dependencies object. This is mutated during the render phase, so
// it cannot be shared with the current fiber.

var currentDependencies = current.dependencies;
workInProgress.dependencies = currentDependencies === null ? null : {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext
}; // These will be overridden during the parent's reconciliation

workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;

// ...省略部分代码

return workInProgress;
}

createWorkInProgress从名称可以看出作用在于创建workInProgress节点, 这个函数的调用时机在cloneChildFibers或者prepareFreshStack中. 这个函数首先判断current.alternate是否为空, 这里解释一下, current.alternate为空说明当前工作单元的子工作单元是更新后新创建的, 这个时候我们会为新的工作单元创建一个Fiber节点, 否则我们对子工作单元进行一个赋值操作即可

全流程

这里梳理一下上述所说的全流程