这是专栏里的文章,专栏里每一篇文章都是接上一篇文章的,是一个非常细的分析过程。
上一篇文章,我们知道render阶段采用深度优先遍历的方式依次执行 beginWork 和 completeWork
completeWork 函数
本篇文章来看看归阶段mount时的流程,也就是 completeWork 所做的工作。
第一次进入
此时已经是归阶段了,第一次进入是img节点,current是null。 然后会根据不同的tag进入不同的case判断
img节点是 HostComponent 所以会进入这个case
case HostComponent:
{
popHostContext(workInProgress);
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type;
// 判断current是否存在,首屏渲染的时候是不存在的,所以首屏不会进入updateHostComponent更新函数
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, type, newProps, rootContainerInstance);
if (current.ref !== workInProgress.ref) {
markRef(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." );
}
}
bubbleProperties(workInProgress);
return null;
}
var currentHostContext = getHostContext();
// popHydrationState是跟ssr相关的
var _wasHydrated = popHydrationState(workInProgress);
if (_wasHydrated) {
if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {
markUpdate(workInProgress);
}
} else {
// 创建对应的DOM节点
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(workInProgress);
}
}
bubbleProperties(workInProgress);
return null;
}
创建对应的DOM节点
createInstance 函数是用来创建对应的DOM节点的函数。
createInstance 函数内部:
function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
var parentNamespace;
{
var hostContextDev = hostContext;
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;
}
var domElement = createElement(type, props, rootContainerInstance, parentNamespace);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
}
上面的代码是 createInstance 源码,内部 createElement 函数执行完 domElement 就是 img fiber 节点对应的是 DOM 元素。
当创建完DOM节点之后,就会将DOM节点插入到已经创建好的DOM树中,appendAllChildren 函数就是进行插入操作的。
接下来是将 DOM 节点保存到 workInProgress.stateNode 属性上。有了DOM节点之后,就会为这个DOM节点添加属性:alt src等。
设置属性的操作都是在 finalizeInitialChildren 中执行的:
finalizeInitialChildren 函数,首先会判断这是否是一个自定义的标签,接下来会根据 host component 的 type 来进入不同的逻辑。
function finalizeInitialChildren(domElement, type, props, rootContainerInstance, hostContext) {
setInitialProperties(domElement, type, props, rootContainerInstance);
return shouldAutoFocusHostComponent(type, props);
}
setInitialProperties 函数里,接下来会判断我们的 props 是否合法,然后进入初始化DOM属性的操作。
setInitialDOMProperties 执行完之后,最终:setAttribute 方法会为我们的属性赋值。当执行完这个操作之后,一个Fiber节点的 completeWork 就完成了。
下一个 fiber 节点的 completeWork 是一个文本节点:
根绝判断进入如下代码:HostText 位置,对文本节点进行处理。
case HostText:
{
var newText = newProps;
if (current && workInProgress.stateNode != null) {
var oldText = current.memoizedProps;
updateHostText(current, workInProgress, oldText, newText);
} else {
if (typeof newText !== 'string') {
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." );
}
}
}
// 校验各种
var _rootContainerInstance = getRootHostContainer();
var _currentHostContext = getHostContext();
var _wasHydrated2 = popHydrationState(workInProgress);
if (_wasHydrated2) {
if (prepareToHydrateHostTextInstance(workInProgress)) {
markUpdate(workInProgress);
}
} else {
// 得到text的DOM节点,加入到:workInProgress.stateNode
workInProgress.stateNode = createTextInstance(newText, _rootContainerInstance, _currentHostContext, workInProgress);
}
}
bubbleProperties(workInProgress);
return null;
}
最后也是得到text的DOM节点,加入到:workInProgress.stateNode。
appendAllChildren 插入DOM节点
当前的 working progress 是 code 节点。
appendAllChildren 每次执行它时,都会将已经创建好的 DOM 节点挂载在当前的 DOM 节点下。
当我们归阶段从每一个子节点向上归到根节点时,创建的每一个子节点的 DOM 元素都会挂载在它的父级 DOM 节点下。
当我们执行到 App 时,我们已经有一颗构建好的 DOM 树。
在 reconcelChildren 中,当某个节点不存在对应的 current 节点时,就不会被标记 Effect tag, 那么首屏渲染这些 DOM 节点是如何挂载在页面中呢?
重新刷新页面,可以看到对于首屏渲染会有一个节点进入到这个逻辑
reconcileSingleElement执行完成之后,会进入到 placeSingleChild 进行对 Fiber 节点处理,newFiber.flags 加入 Placement
最后会生成一颗 Fiber 树。每个 Fiber 节点 child 就是子节点。
总结
mount时归的过程是将节点生成一颗完整的Fiber树,而主要进行归操作的函数就是:completeWork。
mount时的主要进行函数:beginWork & completeWork。
beginWork 目的:为了查找子元素 completeWork 目的:达到最后的子元素往回查找,然后继续执行 beginWork
在执行 completeWork 时,会进行对已经生成好的DOM节点进行插入操作,慢慢的生成一颗完整的Fiber树。