实现DOM diff的update

大家好,我是万维读客的讲师曹欢欢。上节课我们实现了DOM节点新增的diff操作,本节我们看看如何实现update操作。

DOM的UPDATE操作

在写代码之前呢,我们先思考下DOM节点更新要处理哪些事情? 大致可以分为哪些情况?我们需要判断新增、变化、删除这几种情况。

新增:旧的没有,新的有
变化:旧的有,新的有,新的和旧的不一样
删除:旧的有,新的没有

对于DOM节点的操作其实是一样的,要具体处理的事情我们列一下:

1. 事件处理,删除旧的有新的没有的事件,删除变化的事件, 设置新增或者变化的事件
2. 属性处理,删除旧的有新的没有的属性,设置新增的或者变化的属性

实现DOM的Update

按照上面的逻辑,我们可以来编写代码,在commitDOM中增加UPDATE的场景处理,新增updateDom函数,统一对属性和事件做处理,代码如下:

const isNew = (old, next)=>(key)=> !(key in old.props) && (key in next.props);
const isChanged = (old, next)=>(key)=> (key in old.props) && (key in next.props) && old.props[key] != next.props[key];
const isDelete = (old, next)=>(key)=> (key in old.props) && !(key in next.props) ;
...
function commitDOM(fiber) {
    let tempParenNode = null;
    if (fiber.return && fiber.stateNode) {
        tempParenNode = fiber.return;
        while (!tempParenNode.stateNode) {
            tempParenNode = tempParenNode.return;
        }
    }
    if (tempParenNode && fiber.effectTag === 'PLACEMENT') {
        updateDom({props:{}}, fiber);
        tempParenNode.stateNode.appendChild(fiber.stateNode);
    }else if(tempParenNode && fiber.effectTag === 'UPDATE'){
        updateDom(fiber.alternate, fiber);
    }
    if (fiber.child) {
        commitDOM(fiber.child);
    }
    if (fiber.sibling) {
        commitDOM(fiber.sibling);
    }
}
...
function updateDom(pre, next){
    //事件处理
    Object.keys(pre.props).filter(isEvent).filter(
        key=> isDelete(pre, next)(key) || isChanged(pre, next)(key)
    ).forEach(key => {
        const eventName = key.toLowerCase().substring(2);
        next.stateNode.removeEventListener(eventName, next.props[key]);
    });
    Object.keys(next.props).filter(isEvent).filter(
        key=> isNew(pre, next)(key) || isChanged(pre, next)(key)
    ).forEach(key => {
        const eventName = key.toLowerCase().substring(2);
        next.stateNode.addEventListener(eventName, next.props[key]);
    });
    // 属性处理
    Object.keys(pre.props).filter(filerProps).filter(
        key=> isDelete(pre, next)(key)
    ).forEach(key => {
        next.stateNode[key] = '';
    });
    Object.keys(next.props).filter(filerProps).filter(
        key=> isNew(pre, next)(key) || isChanged(pre, next)(key)
    ).forEach(key => {
        next.stateNode[key] = next.props[key];
    });
}

查看我们上上节课增加的测试用例,可以看到测试用例全部通过了,文本节点update正常。

stdout | treact06/jsx.test.jsx > event handler test > render and commit
container.innerHTML <div class="button">2<ol><li>0</li><li>1</li></ol></div>

 ✓ treact06/jsx.test.jsx (5)
   ✓ async render Function Component (1)
     ✓ render with act
   ✓ fiber useState test (1)
     ✓ render useState
   ✓ fiber useReducer test (1)
     ✓ render useReducer
   ✓ event handler test (2)
     ✓ add event
     ✓ render and commit

 Test Files  1 passed (1)
      Tests  5 passed (5)
   Start at  14:01:26
   Duration  49ms

参考

  1. DOM DIFF文档:https://zhuanlan.zhihu.com/p/362539108


请遵守《互联网环境法规》文明发言,欢迎讨论问题
扫码反馈

扫一扫,反馈当前页面

咨询反馈
扫码关注
返回顶部