React-render阶段(二)

概览

React-render阶段一解释了当组件进入reconciler后的执行过程, 从root节点开始调度, 循环调用beginWork创建子节点. 其中创建子节点的过程又分为挂载阶段和更新阶段, 挂载阶段不追踪副作用, 更新阶段追踪副作用, 更新阶段又分为可复用和不可复用, 可复用的会进入bailout的复用逻辑, 会把current树中的当前节点以及其子节点复制到workInProgress树中, 没有进入bailout阶段的Fiber节点会进入diff算法(对应的current中的Fiber节点与返回的JSX对比, 生成新的Fiber节点), 并为新的Fiber节点打上effectTag

当前Fiber节点没有子节点时就进入了completeWork, 可以理解为递归阶段的归阶段, completeWork的目的就是为了创建好对应的dom节点插入对应的父级节点的dom节点, 为其添加副作用标识, 再commit阶段将对应的节点展示到页面上并执行对应的副作用.

以下我以cra创建的项目的代码举例, js如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function App() {
const [num, setNum] = useState(0)
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p onClick={() => { setNum(num + 1) }}>
{num} <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}

CompleteWork执行时机与分析

completeWork发生在当前Fiber节点没有子节点的情况下, 源码发生在performUnitOfWork函数中, 这个函数发生在上文提到过的workLoopSync中, 这个函数将被循环调用

上图打了两个断点处就是completeWork是否执行的条件, 第一个断点拿到的是beginWork创建好的子Fiber节点, 如果没有子Fiber节点则返回null, 只有当nextnull的时候才会进入completeWork, completeWork的开始源于他的上层函数completeUnitOfWork

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
function completeUnitOfWork(unitOfWork) {
var completedWork = unitOfWork; // 获取当前completeWork的Fiber节点

do {
var current = completedWork.alternate; // 获取当前completeWork对应的current树上的节点, 没有则表示是新增的节点
var returnFiber = completedWork.return;

if ((completedWork.flags & Incomplete) === NoFlags) {
setCurrentFiber(completedWork);
var next = void 0;

if ( (completedWork.mode & ProfileMode) === NoMode) {
next = completeWork(current, completedWork, subtreeRenderLanes);
} else {
startProfilerTimer(completedWork);
next = completeWork(current, completedWork, subtreeRenderLanes); // Update render duration assuming we didn't error.

stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
}

resetCurrentFiber();

if (next !== null) {
workInProgress = next;
return;
}
} else {
var _next = unwindWork(completedWork, subtreeRenderLanes); // Because this fiber did not complete, don't reset its expiration time.


if (_next !== null) {
_next.flags &= HostEffectMask;
workInProgress = _next;
return;
}

if ( (completedWork.mode & ProfileMode) !== NoMode) {
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false); // Include the time spent working on failed children before continuing.

var actualDuration = completedWork.actualDuration;
var child = completedWork.child;

while (child !== null) {
actualDuration += child.actualDuration;
child = child.sibling;
}

completedWork.actualDuration = actualDuration;
}

if (returnFiber !== null) {
returnFiber.flags |= Incomplete;
returnFiber.subtreeFlags = NoFlags;
returnFiber.deletions = null;
}
}

var siblingFiber = completedWork.sibling;

if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
workInProgress = siblingFiber;
return;
} // Otherwise, return to the parent


completedWork = returnFiber; // Update the next thing we're working on in case something throws.

workInProgress = completedWork;
} while (completedWork !== null); // We've reached the root.


if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
}

completeUnitOfWork从源码中可以看到是一个do while循环, 终止条件有completeWork !== null或者循环内return前的几个终止条件, 我们可以看到有一个是siblingFiber不为null的情况. 即当前的节点存在兄弟节点时并且已经没有子节点, 当前节点会结束completeWork, 跳出调用栈, 执行下一次循环, 进入兄弟节点的beginWork.当兄弟节点为null的时候, 那么completeWork会被赋值为returnFiber, 这个时候注意并没有用return跳出调用栈, 因为父级节点的beginWork已经被执行, 因此会进入父级节点的completeWork, 由此向上, 当completeWorknull时意味着归到根节点

接下来分析一下completeWork做的具体的事情

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
function completeWork(current, workInProgress, renderLanes) {
console.log('completeWork', 'tag:', workInProgress.tag, ' type:', workInProgress.type);
var newProps = workInProgress.pendingProps;

switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
bubbleProperties(workInProgress);
return null;

case ClassComponent:
{
var Component = workInProgress.type;

if (isContextProvider(Component)) {
popContext(workInProgress);
}

bubbleProperties(workInProgress);
return null;
}

case HostComponent:
{
popHostContext(workInProgress);
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type;

if (current !== null && workInProgress.stateNode != null) {
updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);

if (current.ref !== workInProgress.ref) {
markRef$1(workInProgress);
}
} else {
if (!newProps) {
if (!(workInProgress.stateNode !== null)) {
{
throw Error( "We must have new props for new mounts. This error is likely caused by a bug in React. Please file an issue." );
}
} // This can happen when we abort work.


bubbleProperties(workInProgress);
return null;
}

var currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on whether we want to add them top->down or
// bottom->up. Top->down is faster in IE11.

var _wasHydrated = popHydrationState(workInProgress);

if (_wasHydrated) {
// TODO: Move this and createInstance step into the beginPhase
// to consolidate.
if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {
// If changes to the hydrated node need to be applied at the
// commit-phase we mark this as such.
markUpdate(workInProgress);
}
} else {
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance; // Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.

if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
markUpdate(workInProgress);
}
}

if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef$1(workInProgress);
}
}

bubbleProperties(workInProgress);
return null;
}
// ...以下省略的是针对其他组件的执行逻辑, 这里我们重点关注前几个
}

上面的代码中看到completeWork函数就是针对不同的Fiber节点的Tag, 处理不同的逻辑, 我们根据p标签为例, 会进入caseHostComponent的分支

1
2
3
4
5
6
7
// completeWork顺序如下(展示一部分, 归到p标签为止)
1. img
2. {num}
3. num后面的空格
4. code
5. and save to reload.
6. p

mount阶段

根据上面的completeWork的分析, 我们直接看上面 HostComponent 的逻辑, 一行一行看
开始的逻辑都是一样的, 首先处理context, 获取根容器

  1. popHostContext是和context相关的逻辑, 暂时跳过
  2. rootContainerInstance是获取根容器<div id="root"></div>
    接下来的逻辑会根据挂载和更新进入不同的条件语句, 重新贴一下核心代码
    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
    if (current !== null && workInProgress.stateNode != null) {
    updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);

    if (current.ref !== workInProgress.ref) {
    markRef$1(workInProgress);
    }
    } else {
    if (!newProps) { // 如果没有新的props并且stateNode为null, 可能是React发生了内部错误, 挂载时newProps至少也是一个{}, 一定不会进这里
    if (!(workInProgress.stateNode !== null)) {
    {
    throw Error( "We must have new props for new mounts. This error is likely caused by a bug in React. Please file an issue." );
    }
    } // This can happen when we abort work.
    bubbleProperties(workInProgress);
    return null;
    }

    var currentHostContext = getHostContext(); // context相关
    var _wasHydrated = popHydrationState(workInProgress); // 服务端渲染相关

    if (_wasHydrated) {// 服务端渲染
    if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {

    markUpdate(workInProgress);
    }
    } else {
    var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
    appendAllChildren(instance, workInProgress, false, false);
    workInProgress.stateNode = instance; // Certain renderers require commit-time effects for initial mount.
    // (eg DOM renderer supports auto-focus for certain elements).
    // Make sure such renderers get scheduled for later work.

    if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
    markUpdate(workInProgress);
    }
    }

    if (workInProgress.ref !== null) {
    // If there is a ref on a host node we need to schedule a callback
    markRef$1(workInProgress);
    }
    }
    跳过context相关和服务端渲染相关, 会进入 instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress), 这里主要是创建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
    function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
    var parentNamespace;

    {
    var hostContextDev = hostContext;
    // 检测dom是否正确嵌套
    validateDOMNesting(type, null, hostContextDev.ancestorInfo);

    if (typeof props.children === 'string' || typeof props.children === 'number') {
    var string = '' + props.children;
    var ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo, type);
    validateDOMNesting(null, string, ownAncestorInfo);
    }

    parentNamespace = hostContextDev.namespace;
    }

    // 创建dom实例
    var domElement = createElement(type, props, rootContainerInstance, parentNamespace);
    // 缓存这个fiber节点
    precacheFiberNode(internalInstanceHandle, domElement);
    // 更新fiber节点的props, react会自己定义一个值, 所有的props将存放在当前的dom实例上
    updateFiberProps(domElement, props);
    return domElement;
    }
    返回创建好的domElement, 然后直接插入逻辑, 对应的代码为appendAllChildren(instance, workInProgress, false, false);, 看下这个函数
    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
    appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
    // 拿到当前工作单元的child Fiber节点, 即拿到第一个子节点
    var node = workInProgress.child;

    while (node !== null) {
    if (node.tag === HostComponent || node.tag === HostText) { // 如果是文本节点或者是原生dom节点
    // 这个函数调用的就是parent.appendChild(node.stateNode);
    appendInitialChild(parent, node.stateNode);
    } else if (node.tag === HostPortal) ; else if (node.child !== null) {
    node.child.return = node;
    node = node.child;
    continue;
    }

    if (node === workInProgress) { // 如果是当前工作单元, 插入完毕
    return;
    }

    while (node.sibling === null) { // 没有兄弟节点则Fiber向上冒泡
    if (node.return === null || node.return === workInProgress) {
    return;
    }

    node = node.return;
    }

    node.sibling.return = node.return; // 把兄弟节点的return节点赋值给父节点
    node = node.sibling; // 把node赋值为兄弟节点
    }
    };
    appendAllChildren的作用和函数名相同, 目的就是把当前工作单元的所有子节点全部插入到刚创建好的dom实例中, 全部插入完毕执行workInProgress.stateNode = instance;, 这里采用的是深度优先遍历的方式
    此时这里的instance为插入完的dom实例, 并把对应的节点赋值到当前Fiber节点的stateNode

然后执行的是finalizeInitialChildren方法, 此方法调用了setInitialProperties

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
function setInitialProperties(domElement, tag, rawProps, rootContainerElement) {
var isCustomComponentTag = isCustomComponent(tag, rawProps);

{
validatePropertiesInDevelopment(tag, rawProps);
} // TODO: Make sure that we check isMounted before firing any of these events.


var props;

switch (tag) {
// 跳过一些dom节点的判断逻辑
default:
props = rawProps;
}

// 判断props是否合法
assertValidProps(tag, props);

// 设置初始化的dom属性
setInitialDOMProperties(tag, domElement, rootContainerElement, props, isCustomComponentTag);

switch (tag) {
case 'input':
track(domElement);
postMountWrapper(domElement, rawProps, false);
break;

case 'textarea':
track(domElement);
postMountWrapper$3(domElement);
break;

case 'option':
postMountWrapper$1(domElement, rawProps);
break;

case 'select':
postMountWrapper$2(domElement, rawProps);
break;

default:
if (typeof props.onClick === 'function') {
// TODO: This cast may not be sound for SVG, MathML or custom elements.
trapClickOnNonInteractiveElement(domElement);
}

break;
}
}

上面的函数主要是判断了props是否合法, 并对特殊的dom节点做了一些操作, 并把初始化的属性赋值到当前的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
function setInitialDOMProperties(tag, domElement, rootContainerElement, nextProps, isCustomComponentTag) {
for (var propKey in nextProps) {
if (!nextProps.hasOwnProperty(propKey)) {
continue;
}

var nextProp = nextProps[propKey];

if (propKey === STYLE) { // style的时候
{
if (nextProp) {
Object.freeze(nextProp);
}
}
setValueForStyles(domElement, nextProp);
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) { // dangerouslySetInnerHTML
var nextHtml = nextProp ? nextProp[HTML$1] : undefined;

if (nextHtml != null) {
setInnerHTML(domElement, nextHtml);
}
} else if (propKey === CHILDREN) { // children
if (typeof nextProp === 'string') { // 如果节点是字符串
var canSetTextContent = tag !== 'textarea' || nextProp !== '';

if (canSetTextContent) {
setTextContent(domElement, nextProp);
}
} else if (typeof nextProp === 'number') { // 如果是数字就转换为字符串
setTextContent(domElement, '' + nextProp);
}
} else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING) ; else if (propKey === AUTOFOCUS) ; else if (registrationNameDependencies.hasOwnProperty(propKey)) {
if (nextProp != null) {
if ( typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);
}

if (propKey === 'onScroll') {
listenToNonDelegatedEvent('scroll', domElement);
}
}
} else if (nextProp != null) {
setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag);
}
}
}

复制初始化的props是调用了setInitialDOMProperties, 这个函数循环调用了新的props, 并对每个propKey做了特定的赋值操作, 这一步主要在setValueForProperty中, 这一步会调用node.setAttribute来为创建好的dom元素设置属性

继续走下去进行的是判断是否存在ref, 如果存在ref则调用markRef$1(workInProgress);函数

最后执行bubbleProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if ( (completedWork.mode & ProfileMode) !== NoMode) {
var actualDuration = completedWork.actualDuration;
var treeBaseDuration = completedWork.selfBaseDuration;
var child = completedWork.child;

while (child !== null) {
newChildLanes = mergeLanes(newChildLanes, mergeLanes(child.lanes, child.childLanes));
subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;

actualDuration += child.actualDuration;
treeBaseDuration += child.treeBaseDuration;
child = child.sibling;
}

completedWork.actualDuration = actualDuration;
completedWork.treeBaseDuration = treeBaseDuration;

bubbleProperties 根据fiber.childfiber.child.sibling更新subtreeFlagschildLanes, 主要是为了标记子树有没有更新, 这样可以通过 fiber.subtreeFlags 快速判断子树是否有副作用钩子,不需要深度遍历. 在React17版本后使用subtreeFlags替换了finishWork.firstEffect的副作用链表, 操作主要发生在bubbleProperties函数中, 核心代码如下

update阶段

当进入update阶段, 假如我们把p节点的Fiber作为案例, 对应case为HostComponent会进入以下的条件分支

1
2
3
4
5
6
7
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);

if (current.ref !== workInProgress.ref) {
markRef$1(workInProgress);
}
}

updateHostComponent$1代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
updateHostComponent$1 = function (current, workInProgress, type, newProps, rootContainerInstance) {
var oldProps = current.memoizedProps; // 此状态为更新, 获取current的props

if (oldProps === newProps) { // 判断props是否相同, 相同表示进入了bailout阶段,哪怕children变了我们也不需要做什么操作,因此直接跳过
return;
}

var instance = workInProgress.stateNode; // 获取实例
var currentHostContext = getHostContext();

var updatePayload = prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, currentHostContext);

workInProgress.updateQueue = updatePayload;

if (updatePayload) {
// 标记更新, 内部直接设置workInProgress.flags |= Update
markUpdate(workInProgress);
}
};

这里调用了一个主要的更新方法为prepareUpdate, 返回的updatePayload将被加入工作单元的更新队列中, 这个函数调用了diffProperties, 其中返回的updatePayload是一个数组, 第i项是对应的propKey, 第i + 1项是对应的value, 当存在updatePayload的时候意味着这个HostComponent存在增,或者更新的情况, 会调用markUpdate进行更新

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
function diffProperties(domElement, tag, lastRawProps, nextRawProps, rootContainerElement) {
{
validatePropertiesInDevelopment(tag, nextRawProps);
}

var updatePayload = null;
var lastProps;
var nextProps;

switch (tag) {
// 这里省略了对特性的dom标签比如(input, select等)赋值lastProps和nextProps的过程
default:
lastProps = lastRawProps;
nextProps = nextRawProps;

if (typeof lastProps.onClick !== 'function' && typeof nextProps.onClick === 'function') {
trapClickOnNonInteractiveElement(domElement);
}

break;
}

// 检验props
assertValidProps(tag, nextProps);
var propKey;
var styleName;
var styleUpdates = null;

for (propKey in lastProps) {
// 针对删除的情况, 需要标记对应的propKey为null
if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] == null) {
continue;
}

if (propKey === STYLE) {
var lastStyle = lastProps[propKey];

for (styleName in lastStyle) {
if (lastStyle.hasOwnProperty(styleName)) {
if (!styleUpdates) {
styleUpdates = {};
}

styleUpdates[styleName] = '';
}
}
} else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) ; else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING) ; else if (propKey === AUTOFOCUS) ; else if (registrationNameDependencies.hasOwnProperty(propKey)) {
if (!updatePayload) {
updatePayload = [];
}
} else {
(updatePayload = updatePayload || []).push(propKey, null);
}
}

for (propKey in nextProps) {
var nextProp = nextProps[propKey];
var lastProp = lastProps != null ? lastProps[propKey] : undefined;
// 针对新增或者更新的情况, 需要标记对应的propKey为null
if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || nextProp == null && lastProp == null) { // 对应props被删除的情况
continue;
}

if (propKey === STYLE) {
{
if (nextProp) {
Object.freeze(nextProp);
}
}

if (lastProp) {
// 在 `lastProp` 上取消设置样式,但不在 `nextProp` 上设置.
for (styleName in lastProp) {
if (lastProp.hasOwnProperty(styleName) && (!nextProp || !nextProp.hasOwnProperty(styleName))) {
if (!styleUpdates) {
styleUpdates = {};
}

styleUpdates[styleName] = '';
}
} // 从lastProps中的style更新数据.


for (styleName in nextProp) {
if (nextProp.hasOwnProperty(styleName) && lastProp[styleName] !== nextProp[styleName]) {
if (!styleUpdates) {
styleUpdates = {};
}

styleUpdates[styleName] = nextProp[styleName];
}
}
} else {
if (!styleUpdates) {
if (!updatePayload) {
updatePayload = [];
}

updatePayload.push(propKey, styleUpdates);
}

styleUpdates = nextProp;
}
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
var nextHtml = nextProp ? nextProp[HTML$1] : undefined;
var lastHtml = lastProp ? lastProp[HTML$1] : undefined;

if (nextHtml != null) {
if (lastHtml !== nextHtml) {
(updatePayload = updatePayload || []).push(propKey, nextHtml);
}
}
} else if (propKey === CHILDREN) {
if (typeof nextProp === 'string' || typeof nextProp === 'number') {
(updatePayload = updatePayload || []).push(propKey, '' + nextProp);
}
} else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING) ; else if (registrationNameDependencies.hasOwnProperty(propKey)) {
if (nextProp != null) {
if ( typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);
}

if (propKey === 'onScroll') {
listenToNonDelegatedEvent('scroll', domElement);
}
}

if (!updatePayload && lastProp !== nextProp) {
updatePayload = [];
}
} else if (typeof nextProp === 'object' && nextProp !== null && nextProp.$$typeof === REACT_OPAQUE_ID_TYPE) {
nextProp.toString();
} else {
(updatePayload = updatePayload || []).push(propKey, nextProp);
}
}

if (styleUpdates) {
{
validateShorthandPropertyCollisionInDev(styleUpdates, nextProps[STYLE]);
}

(updatePayload = updatePayload || []).push(STYLE, styleUpdates);
}

return updatePayload;
}

执行完这个diffProperties, 再执行bubbleProperties(workInProgress), 然后就结束了当前节点的completeWork

注意点

  1. Fibertagfunction的时候是不会进入completeWork
  2. 挂载的时候插入的dom节点的获取方式在于完成的finishedWork, 在performSyncWorkOnRoot函数中
  3. React17之前原本有一根finishWork.firstEffect开始的副作用链表, 始终指向的是第一个产生副作用的链表, 链表的nextEffect指向的是下一个具有副作用的链表, 这根链表在React17版本后使用subtreeFlags替换了, 操作主要发生在bubbleProperties函数中

全流程

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