复制一份minivue(完)

201 阅读3分钟

「这是我参与2022首次更文挑战的第26天,活动详情查看:2022首次更文挑战

为啥需要diff算法,简单说,就是我们想只更新需要更新的节点,减少dom操作,提倡节约,拒绝浪费。能在原节点上修改的就修改,不能再重新创建新的。 那么为啥需要vnode , 因为从dom上读取信息比较慢,原生dom节点上又有许多我们不关心的属性,所以来了个我们全部关心的vndode.

要对比,就要只要新老数据,我们只需要记住老的数据,然后和每次新的数据比较即可。然后我们的mount也需要一个状态标识。这里,暂且直接就挂在组件实例上。未加载才需要走mount,已加载就需要走更新,也就是diff

开始diff

image.png

然后为了后面的diff操作,我们需要把编译出来的dom节点挂到vNode上,就用el属性了。

diff的具体情况,分类讨论。 下面就用n1 ,n2表示老、新vNode

直接比较虚拟节点的存在

最简单的有两种情况

1 n1为空 n2存在,直接加载n2即可

2 n1存在 n2为空, 卸载n1.el即可。

比较tagName

然后就是比较标签了,标签不同当然不能复用,标签对上了再进行下一步。

比较属性值

再然后就是比较props,也分两种情况

  1. 新值和老值都有,或者只有新值有, 只要不等就重设 setAttribute。因为新值有老值,老值没有也属于不等。
  2. 剩余新值没有,而老值有的,就全部移除 removeAttribute()
 let {el} = n1 ;
    /* tag */
    if ( n1.tag !== n2.tag){
        /* 这个api还在实验阶段 */
        // n1.el.replaceWith(document.createElement(n2.tag));
        /* new , old */
        let newel = document.createElement(n2.tag) ;
        /* 编译这个新的Vnode 但不直接追加。而是把编译后的节点替换老节点*/
        mountElement(n2, parent, true)
     return    parent.replaceChild(  n2.el,n1.el)

比较子节点

最后就是比较麻烦的children了。这里因为我们的children可以是字符串,所以也分两种情况

  1. 新值是字符串,只需要比较新老是否相等,不等就replace文本节点

  2. 新值是数组,老值是字符串,直接遍历新值 diff. image.png

  3. 新值是数组,老值也是数组,我们的麻烦来了。写个什么算法是不可能的,先实现其功能,我们先假设子节点的基本顺序不变。然后就遍历diff 。 如果老数组比新数组要长, 长的那部分就 unmoutElement.

image.png

这里没有用vue里面用到一些算法,就是一个一般的diff方式。 因为是简单复刻,明白其基本流程,vue源码里的逻辑确实很复杂,因为考虑的情况多,但是基本流程就是这样的。 接下了来,要继续看头疼的源码了。

最后也是相当重要的---测试

写完了要立即测试, 不测就不知道有问题,。上述代码是已经测试验证了的。确实遇到了很多问题。 说一下测试的手段, 因为我们就直接写了一个十分简单的核心,不能正常测试。所以就把一些关键的数据挂到全局,然后直接在控制台修改就能看到结果了。 下面就是把响应式数据和vnode直接关联起来了,以便触发我们的逻辑。

image.png

image.png

image.png