这篇文章讲一下render
阶段的第二部分:completeUnitOfWork
。
react
的render
阶段,会按照先序遍历的顺序构建fiber
树,当workInProgress
指针来到一个叶子节点时,就会执行completeUnitOfWork
方法,这个方法会修改workInProgress
指针,让其指向先序遍历中的下一个节点,下面,就来介绍一下completeUnitOfWork
完成的工作。
completeUnitOfWork
主要做了三件事:dom
节点的创建和更新,effectList
的收集和错误的处理。关于错误处理的内容,笔者也没有深入了解过,这篇文章就介绍一下前两部分内容:dom
节点的创建和更新。
Dom节点的创建和更新
这部分的内容在completeWork
函数中。completeWork
会根据不同的组件类型进入不同的逻辑。由于completeWork
主要会做dom
相关的操作,因此我们可以主要看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 {
// 新增dom节点
// ...
var currentHostContext = getHostContext();
var _wasHydrated = popHydrationState(workInProgress);
if (_wasHydrated) {
// 服务端渲染相关
} else {
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
markUpdate(workInProgress);
}
}
if (workInProgress.ref !== null) {
markRef$1(workInProgress);
}
}
return null;
可以看到,对于HostComponent
的处理分为dom
更新和dom
创建两个过程。针对更新,主要是dom
属性的更新,对于新增的dom
节点,会先创建一个dom
节点,之后将该节点挂载到fiber
的stateNode
属性上,之后插入该节点(appendAllChildren
)。由于在fiber
架构中,组件树和dom
并不是对应的,比如函数组件和类组件不存在对应的dom
节点,因此appendAllChildren
这部分的逻辑比较繁琐,这里就不展开讲解了,有兴趣的可以看一下@Axizs大佬的文章。
effectList收集
在完成dom
相关的更新之后,回到completeUnitOfWork
中,会开始effectList
链的收集。effectList
链的作用就是将有副作用的fiber
节点收集为一条链表,这样在commit
阶段中,就可以根据effectList
来进行操作,不需要再遍历一次fiber
树。下面看一下这部分代码
// returnFiber就是当前正在处理的workInProgress节点的父节点
// completedWork就是正在处理的fiber节点
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = completedWork.firstEffect;
}
// 将completedWork身上的effectList拼接到returnFiber的后面
if (completedWork.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
}
returnFiber.lastEffect = completedWork.lastEffect;
}
var flags = completedWork.flags;
// 将completedWork自己拼接到returnFiber的后面
if (flags > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork;
} else {
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect = completedWork;
}
也就是说,effectList
的顺序是从层次最深的子节点开始向上收集,层次最深的fiber
节点位于effectList
链表的头部。最终,workInProgress
树的rootFiber
节点会得到完整的effectList
。不是很清楚的同学可以看一下b站的这个视频。
注意点
关于completework
部分有一些需要注意的地方:
completeWork
中创建了新的dom
节点,但是此时,这些新的dom
节点并没有被插入到dom
树中,比如看下面的例子
import { useState, useCallback } from 'react'
const App = () => {
const [visible, change] = useState(false)
const changeVisible = useCallback(() => change((pre) => !pre), [change])
return (
<div>
{visible && (
<div>
<h1>新的div</h1>
</div>
)}
<p>老的内容</p>
<button onClick={changeVisible}>click</button>
</div>
)
}
export default App
点击按钮,visible
变为true
,此时,在completework
中,h1
被添加到了div
中,但是div
并没有被添加到dom
树中。相反,这个div
会被打上placement
的tag
,从而被收集到effectList
中,在之后的commit
阶段中,这个div
会被添加到真实的dom
树中,从而实现页面的更新。
在completeWork
之后,render
阶段结束,进入commit
阶段,之后的文章会为大家讲解。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!