经过上一章,我们得到的FilberNode已经具有了child和return属性。一颗Filber树的结构已经展现出来了。
那我们最终是想在页面渲染真实的DOM。所以我们现在要在completeWork里,构建出一颗离屏的DOM树。
之前在说FilberNode的属性时,我们提到过一个属性stateNode。它就是用来保存每个FilberNode的真实DOM。
OK,现在我们开干,准备实现completeWork。
在completeWork方法里,我们将刚才准备好的FilberNode(最外层的)传进来。
completeWork方法也一定是一个递归的过程,每次调用的时候我们要判断当前的FilberNode的tag类型。
在判断当前的FilberNode是否具有stateNode,如果有,就说明不是第一次更新。如果没有,就说明此时是mount第一次渲染的阶段。
export const completeWork = (filberNode) => {
console.log(filberNode);
const tag = filberNode.tag
switch (tag) {
case HostComponent: {
if(filberNode.stateNode !== null){
//更新
}else{
completeHostComponent(filberNode)
}
break;
}
case HostText: {
break;
}
case HostRoot: {
}
}
}
拿到每个FilberNode后,如果他是HostComponent类型的。我们实现出completeWork方法。
首先我们可以通过拿到FilberNode的type(div,span),知道真实DOM的类型,通过document的方法创建出对应的element。
然后再通过return属性,拿到当前节点的父节点。并且在父节点的stateNode中,添加创建好的element。
function completeHostComponent(filberNode) {
const type = filberNode.type;
const element = document.createElement(type);
filberNode.stateNode = element;
const parent = filberNode.return;
if(parent && parent.stateNode && parent.tag === HostComponent) {
parent.stateNode.appendChild(element)
}
completeWork(filberNode.child)
}
对于HostText类型,我们可以直接创建文本节点,然后挂载在stateNode上面即可:
function completeHostText(filberNode) {
const content = filberNode.pendingProps;
const element = document.createTextNode(content)
filberNode.stateNode = element
const parent = filberNode.return;
if(parent && parent.stateNode && parent.tag === HostComponent) {
parent.stateNode.appendChild(element)
}
}
这里值得注意的是,在创建真实DOM的时候,这里并没有处理props相关的内容。只创建了对应的DOM结构。
最后我们将执行完beginWork和completeWork的FilberRootNode。挂载在hostRootFilber上面,
function updateContainer(root, element) {
const hostRootFilber = root.current;
const update = createUpdate(element);
hostRootFilber.updateQueue = createUpdateQueue()
enqueueUpdate(hostRootFilber.updateQueue, update);
beginWork(hostRootFilber);
completeWork(hostRootFilber);
root.finishedWork = hostRootFilber;
}
root的current和finishedWork其实是一模一样的,但React其实并非是这样处理的。经过beginWork和completeWork处理的节点,并不是最外层的FilberNode,而是它的alternate。
所以我们调用beginWork和completeWork处理的应该是FilberNode的alternate。
为了看到效果我们可以写一个方法,当然这个方法里面内容不可能这没少。但是我们可以先实现一下:
export function commitWork(filberRootNode) {
const container = filberRootNode.container;
const child = filberRootNode.finishedWork.child.stateNode;
container.appendChild(child)
}
然后再beginWork和completeWork执行完后,执行这个方法。
就可以看到页面正产渲染节点了。