如果next vnode和prev vnode只是位置移动,vnodedata和children没有任何变动,调用patchVnode之后不会有任何dom操作。
接下来只需要把这个key相同的vnode移动到正确的位置即可。我们的问题变成了怎么移动。
首先需要知道两个事情:
每一个prev vnode都引用了一个真实dom节点,每个next vnode这个时候都没有真实dom节点。
调用patchVnode的时候会把prevVnode引用的真实Dom的引用赋值给nextVnode,就像这样:
还是拿上面的例子,外层遍历next vnode,遍历第一个元素的时候, 第一个vnode是li©,然后去prev vnode里找,在最后一个节点找到了,这里外层是第一个元素,不做任何移动的操作,我们记录一下这个vnode在prevVnode中的索引位置lastIndex,接下来在遍历的时候,如果j<lastIndex,说明原本prevVnode在前面的元素,在nextVnode中变到了后面来了,那么我们就把prevVnode[j]放到nextVnode[i-1]的后面。
这里多说一句,dom操作的api里,只有insertBefore(),没有insertAfter()。也就是说只有把某个dom插入到某个元素前面这个方法,没有插入到某个元素后面这个方法,所以我们只能用insertBefore()。那么思路就变成了,当j<lastIndex的时候,把prevChildren[j]插入到nextVnode[i-1]的真实dom的后面元素的前面。
当j>=lastIndex的时候,说明这个顺序是正确的的,不用移动,然后把lastIndex = j;
也就是说,只把prevVnode中后面的元素往前移动,原本顺序是正确的就不变。
现在我们的diff的代码变成了这样:
同样的问题,如果新旧vnode的元素数量一样,那就已经可以工作了。接下来要做的就是新增节点和删除节点。
首先是新增节点,整个框架中将vnode挂载到真实dom上都调用patch函数,patch里调用createElm来生成真实dom。按照上面的实现,如果nextVnode中有一个节点是prevVnode中没有的,就有问题:
在prevVnode中找不到li(d),那我们需要调用createElm挂在这个新的节点,因为这里的节点需要超入到li(b)和li©之间,所以需要用insertBefore()。在每次遍历nextVnode的时候用一个变量find=false表示是否能够在prevVnode中找到节点,如果找到了就find=true。如果内层遍历后find是false,那说明这是一个新的节点。
我们的createElm函数需要判断一下第四个参数,如果没有就是用appendChild直接把元素放到父节点的最后,如果有第四个参数,则需要调用insertBefore来插入到正确的位置。
接下来要做的是删除prevVnode多余节点:
在nextVnode中已经没有li(d)了,我们需要在执行完上面所讲的所有流程后在遍历一次prevVnode,然后拿到nextVnode里去找,如果找不到相同key的节点,那就说明这个节点已经被删除了,我们直接用removeChild方法删除Dom。