DiffDOM原理和Create实现
大家好,我是万维读客的讲师曹欢欢。上节我们拆分了render和commit阶段,这里我们开始处理render中的DOM Diff操作,区分不同状态。
DiffDOM原理
DOM树的diff算法主要是用来处理DOM更新时的操作,只需要差异化更新变更的DOM节点即可,而不需要重新渲染整个DOM树。
DOM diff算法有个约定,通过约定可以将diff算法的复杂度降低到O(N),一次遍历即可解决。
- DOM节点跨级移动较少,可以忽略不计
- 相同类型的组件会产生类似的结构,不同类型的组件产生的结构不同
- 同一级别的节点可以通过key区分不同
实现DOMDiff
上节我们拆分的render阶段,增加了函数reconcile, DOM的处理主要在这个函数里,我们需要区分出来DOM的mount、update、delete三种情况。修改代码如下:
function reconcile(fiber) {
// 用链表处理child
let preSibling = null;
// dom diff: mount/update/delete
let oldFiber = fiber.alternate?.child;
// fiber.props?.children.forEach((child, idx) => {
let idx = 0;
while (idx < fiber.props?.children.length || oldFiber) {
const child = fiber.props?.children[idx];
let newFiber = null;
let isSameType = oldFiber && child && oldFiber.type == child.type;
if (child && (!oldFiber || !isSameType)) {
// mount
newFiber = {
type: child.type,
stateNode: null,
props: child.props,
return: fiber,
alternate: null,
child: null,
sibling: null,
effectTag: 'PLACEMENT'
}
} else if (isSameType && oldFiber) {
// update
newFiber = {
type: child.type,
stateNode: oldFiber.stateNode,
props: child.props,
return: fiber,
alternate: oldFiber,
child: null,
sibling: null,
effectTag: 'UPDATE'
}
} else if (!isSameType && oldFiber) {
// delete
}
if (idx == 0) {
fiber.child = newFiber;
} else {
preSibling.sibling = newFiber;
}
if (oldFiber) {
oldFiber = oldFiber.sibling;
}
preSibling = newFiber;
idx++;
}
}
修改commit
在reconcile处理阶段我们给不同的fiber打上了不同的effectTag标签,这里我们对不同的类型,进行不同的commit操作。
function commitDOM(fiber) {
let tempParenNode = null;
if (fiber.return && fiber.stateNode) {
tempParenNode = fiber.return;
while (!tempParenNode.stateNode) {
tempParenNode = tempParenNode.return;
}
}
// mount情况
if (tempParenNode && fiber.effectTag === 'PLACEMENT') {
// 属性处理,来自于上一节reconcile中
Object.keys(fiber.props).filter(filerProps).forEach(key => {
fiber.stateNode[key] = fiber.props[key];
})
Object.keys(fiber.props).filter(isEvent).forEach(key => {
const eventName = key.toLowerCase().substring(2);
fiber.stateNode.addEventListener(eventName, fiber.props[key]);
})
tempParenNode.stateNode.appendChild(fiber.stateNode);
}
if (fiber.child) {
commitDOM(fiber.child);
}
if (fiber.sibling) {
commitDOM(fiber.sibling);
}
}
然后查看我们的测试用例,正常通过,说明新增的DOM处理是没问题的。
参考
- DOM DIFF文档:https://zhuanlan.zhihu.com/p/362539108