Vue diff algorithm and virtual dom knowledge collation (13) Handwritten patch child node update transposition strategy

In the previous article, we wrote the logic of inserting nodes in the patch where both old and new nodes have child nodes,
but we haven’t dealt with the order or number of child nodes of the old node, so let’s continue now

Let's take a look at how it was originally written.
We open our case and find the init.ts file under snabbdom/src under node_modules.
insert image description here
We find an updateChildren function in it
insert image description here
. Here we see that various new and old variables are defined here. The definition of
insert image description here
the following is still to start a loop to judge the difference between the old and the new, and whether the old and the new are the same, etc. It is
really different for different situations
insert image description here
. Let’s write one ourselves.

We create a patchVNode.js under snabbdom under the src directory of the case
and then we open patch.js to find

if(oldVnode.sel == newVnode.sel && oldVnode.key == newVnode.key) {
    
    

Get the code inside into patchVNode.js
insert image description here

The patchVNode.js reference code is as follows

import createElement from "./createElement";
export default function(newVnode, oldVnode) {
    
    
    //判断  如果 新旧虚拟节点完全相同 则不做操作  直接返回
    if(oldVnode === newVnode) return;
    /*
        判断新节点的text是不是空的  包括就算text是空子符串  也算有 只要不是undefined未定义
        同时  也要判断  要newVnode没有children  或 children 是个空数组
    */
    if(newVnode.text != undefined&&(!newVnode.children||!newVnode.children.length)) {
    
    
        //确定一下新旧节点的text文本是不是不同  如果相同就什么都不用做了
        if(newVnode.text !== oldVnode.text) {
    
    
            //利用innerText将新节点的text写入到老节点的elm中 因为  elm  存的是真实的dom
            oldVnode.elm.innerText = newVnode.text;
        }
    }else{
    
    
    //否则就算新节点没有text
        //判断老节点有没有children
        if(oldVnode.children&&oldVnode.children.length) {
    
    
            //当新旧节点都有子节点
            //定义un用于记录当前更新的元素下标
            let un = 0;
            //遍历写节点的子元素
            newVnode.children.map((news,index) => {
    
    
                //定义 isExist  判断新节点中有没有和旧节点一样的元素
                let isExist = false;
                //遍历旧节点子集
                oldVnode.children.map(old => {
    
    
                    //判断  如果有一模一样的元素  什么都不用做
                    if(old.sel == news.sel &&old.key == news.key) {
    
    
                        isExist = true;
                    }
                })
                //判断  如果在旧节点中没有找到相同的节点 就输出出来
                if(!isExist){
    
    
                    //通过createElement将虚拟节点变成真正的孤儿节点
                    let dom = createElement(news);
                    //当当前下标的子节点的elm属性记录上这个新创建的孤儿节点
                    news.elm = dom;
                    //判断un是不是已经大于了子节点创的   如果是 表示这个下标在老节点找不到
                    if(un < oldVnode.children.length) {
    
    
                        //在老节点对应un下标的前面插入这个节点
                        oldVnode.elm.insertBefore(dom, oldVnode.children[un].elm);
                    }else{
    
    
                        //直接在老节点的最后面插入
                        oldVnode.elm.appendChild(dom);
                    }
                }else{
    
    
                    //否则就表示 本次循环中 子元素找到了相同的元素 将un+1
                    un++;
                }
            })
        } else {
    
    
            //新的有children  老的没有
            //通过innerHTML 清空老节点中的内容
            oldVnode.elm.innerHTML = "";
            //二者都有children  就算最复杂的情况
            newVnode.children.map(item => {
    
    
                let dom = createElement(item);
                oldVnode.elm.appendChild(dom);
            })
        }
    }
}

Simply put, it is to move all the logic of fine processing after judging as the same node to patchVNode

Then we patch.js introduce patchVNode

import patchVNode from "./patchVNode";

Then the logic removed just now is still useful.
We call patchVNode at the location where the code is removed.

patchVNode(newVnode, oldVnode);

insert image description here
Of course, we still have to verify to see if everything is still normal.
We change the code of the index.js entry file under src as follows

import h from "./snabbdom/h";
import patch from "./snabbdom/patch";

const container = document.getElementById("container");

const vnode = h("section", {
    
    }, [
  h("p", {
    
    key:"a"}, "a"),
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"c"}, "c")
]);

patch( container, vnode)

const btn = document.getElementById("btn");
const vnode1 = h("section", {
    
    },[
  h("p", {
    
    key:"a"}, "a"),
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"c"}, "c"),
  h("p", {
    
    key:"d"}, "d")
]);

btn.onclick = function(){
    
    
  patch( vnode, vnode1)
}

Run the project
insert image description here
and click to change dom
insert image description here
to see that the function is still normal

Then create an updateChildren.js under snabbdom under the src directory

We will write the logic in updateChildren here

We now write these codes in updateChildren

import patchVNode from "./patchVNode";

//判断是否为同一个虚拟节点
function checkSameVnode(a,b) {
    
    
    return a.sel == b.sel && a.key == b.key;
}

export default function(parentElm, oldch, newCh) {
    
    
    // 旧前
    let oldStartIdx = 0;
    // 新前
    let newStartIdx = 0;
    // 旧后
    let oldEndIdx = oldch.length - 1;
    // 新后
    let newEndIdx = newCh.length - 1;
    // 旧前节点
    let oldStartVnode = oldch[0];
    // 旧后节点
    let oldEndVnode = oldch[oldEndIdx];
    // 新前节点
    let newStartVnode = newCh[0];
    // 新后节点
    let newEndVnode= newCh[newEndIdx];


    /*
        定义一个while循环语句 来处理
        只要  旧前小于等于旧前 而且 新前小于等于新后 他就会一直执行
        也可以理解为  只要 新前大于了新后   或者  旧前大于了旧后  这个循环就停了
    */
    while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    
    
        //判断 新节点和旧节点的第一个子节点是否相同  或者是 判断  旧前节点和新前节点是不是同一个虚拟节点
        if(checkSameVnode(oldStartVnode,newStartVnode)) {
    
    
            //直接用patchVNode 对他进行精细化比较
            patchVNode(newStartVnode,oldStartVnode);
            //将新前和旧前都  +1  新前和旧前节点都下一一个节点指向
            oldStartVnode = oldch[++oldStartIdx];
            newStartVnode = newCh[++newStartIdx];
        }
    }
}

Here we roughly understand the logic, which is to define the new, old, and old nodes that need to be used for judgment at the beginning.
Then we start a loop to process all child nodes.
Here we only write the first judgment. We judge the first and old nodes of the new node. Is the first child node the same virtual node? If so, call patchVNode to continue to do fine-grained comparison
so that they are recursive before

Then we come to
patchVNode.js
to introduce updateChildren first

import updateChildren from "./updateChildren";

then we find

if(oldVnode.children&&oldVnode.children.length) {
    
    

Remove the code inside
and call updateChildren

updateChildren(oldVnode.elm, oldVnode.children, newVnode.children);

insert image description here
Simply put, we don’t do the fine-grained comparison before the sub-nodes, we will hand it over to updateChildren.

Then we change the index code under src as follows

import h from "./snabbdom/h";
import patch from "./snabbdom/patch";

const container = document.getElementById("container");

const vnode = h("section", {
    
    }, [
  h("p", {
    
    key:"a"}, "a"),
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"c"}, "c")
]);

patch( container, vnode)

const btn = document.getElementById("btn");
const vnode1 = h("section", {
    
    },[
  h("p", {
    
    key:"a"}, "AAA"),
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"c"}, "c")
]);

btn.onclick = function(){
    
    
  patch( vnode, vnode1)
}

You can see that we changed the text of the first a node
and then run the project
insert image description here
Click to change dom

insert image description here
In this way, we have written the refined comparison of the first node.

Then the index.js code can be changed as follows

import h from "./snabbdom/h";
import patch from "./snabbdom/patch";

const container = document.getElementById("container");

const vnode = h("section", {
    
    }, [
  h("p", {
    
    key:"a"}, "a"),
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"c"}, "c")
]);

patch( container, vnode)

const btn = document.getElementById("btn");
const vnode1 = h("section", {
    
    },[
  h("p", {
    
    key:"a"}, [
    h("p", {
    
    }, "123"),
    h("p", {
    
    }, "3123"),
    h("p", {
    
    }, "121231233")
  ]),
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"c"}, "c")
]);

btn.onclick = function(){
    
    
  patch( vnode, vnode1)
}

Run the project
insert image description here
Click to change dom

insert image description here
To put it simply, the logic is that two nodes enter the patch
because neither of them has a defined key, they are all section tags,
so when they reach the same node, they enter patchVNode
insert image description here
and then pass the first condition because both the old and new nodes have no text, and then judge their subsections. The condition
entered updateChildren
insert image description here
because the first child nodes of the old and new nodes are both p-labeled and the key is a, so entering this condition again uses the two first child nodes as parameters for refined comparison and then
insert image description here
judges our new node here There are three sub-nodes in a, so it is judged that the text cannot be reached,
and then the second condition is that the a of our old node has text and no sub-nodes, so we
go to the last else
and then we clear the content of the old node and
replace the sub-nodes of the new node. Insert the node
insert image description here
and then add an additional condition to if in the updateChildren.js loop

else if(checkSameVnode(oldEndVnode, newEndVnode)) {
    
    
    //将两个节点的最后一个节点做精细化比较
    patchVnode(newEndVnode,oldEndVnode);
    //新后旧后节点与表示向前推一个
    oldEndVnode = oldch[--oldEndIdx];
    newEndVnode = newCh[--newEndIdx];
}

insert image description here
One of our treatments of the new post and old post nodes

Then we run the project
insert image description here
and click to change dom
insert image description here
so that the refined comparison of the last node is also out

Then there is a more magical operation to judge the old front and new back,
which is to judge whether the first child element of the old node is at the end of your new node

We add an additional if condition in the updateChildren.js loop

//然后判断  旧前与新后是不是同一个节点
}else if(checkSameVnode(oldStartVnode, newEndVnode)) {
    
    
    //拿旧后和新前做精细化比较
    patchVNode(oldStartVnode, newEndVnode);
    //用parentElm父级调用insertBefore将旧前节点  插入到旧后节点的下一个兄弟节点的前面
    parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling);
    //后移旧前节点
    oldStartVnode = oldch[++oldStartIdx];
    //前移新后节点
    newEndVnode = newCh[--newEndIdx];
}

insert image description here

Then we modify the index.js code under src as follows

import h from "./snabbdom/h";
import patch from "./snabbdom/patch";

const container = document.getElementById("container");

const vnode = h("section", {
    
    }, [
  h("p", {
    
    key:"a"}, "a"),
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"c"}, "c")
]);

patch( container, vnode)

const btn = document.getElementById("btn");
const vnode1 = h("section", {
    
    },[
  h("p", {
    
    key:"c"}, "c"),
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"a"}, "a")
]);

btn.onclick = function(){
    
    
  patch( vnode, vnode1)
}

We completely reverse the order of the child nodes of the two nodes.
insert image description here
We click to change the dom
insert image description here
so that our order is reversed.

Let me briefly talk about the logic. The first time I come in to judge the new and the old, they are all with the p tag key as a,
so I will compare the old and the new with a small update to ensure that the content of the old and the new will be the same.
Then here It involves a knowledge point of insertBefore. If the new node you put in is not on the DOM tree, it will insert it for you. If it has already been on the tree, it will become a move, because the old node must have been on the tree, so elm represents this node
. The node on the dom tree
, so the logic here is to move the old front elm to the front of the next brother node of the old back node's elm. You can also simply understand that it is inserted behind the old back node and you don't need to worry about taking the brother node. If you can’t get it, you can insert it.
At this time, the old node is still the c node. There is no node behind it, so a is moved to the end.

Then the second time
, because the new post was pointed to the previous node of a, the old front was also moved down to point to the next child node of the old node. Therefore,
condition 3 has reached the key of the old front p label is b, and the new post p label b
Then refine the two nodes
and then insert b in front of the old back node because we have not changed the old back node because our condition three just moves the old front down and the new back up, so
even though you have inserted a after c c is still the old back node
, so you insert b before the next sibling node of c It became the cba I saw
, but it’s not over yet, and there’s another cycle. c comes in.
At this time, the first condition is met.
The old and new fronts are the same.
They are the c nodes. Make a refined comparison of them and update the content of the c node.

Then we have achieved our current effect

Then there is the last case where
the old post matches the new pre

//判断当旧后和新前为同一个节点时
}else if (checkSameVnode(oldEndVnode, newStartVnode)) {
    
    
    //对旧后与新前最精细化比较
    patchVNode(oldEndVnode, newStartVnode);
    //还是那个insertBefore特性 dom节点存在就是移动 不是插入  将 旧后移动到旧前的前面
    parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm);
    //将旧后节点前移
    oldEndVnode = oldch[--oldEndIdx];
    //将新前节点后移
    newStartVnode = newCh[++newStartIdx];
}

insert image description here
Then we change the index under src to this

import h from "./snabbdom/h";
import patch from "./snabbdom/patch";

const container = document.getElementById("container");

const vnode = h("section", {
    
    }, [
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"a"}, "a"),
  h("p", {
    
    key:"c"}, "c")
]);

patch( container, vnode)

const btn = document.getElementById("btn");
const vnode1 = h("section", {
    
    },[
  h("p", {
    
    key:"c"}, "c"),
  h("p", {
    
    key:"b"}, "b"),
  h("p", {
    
    key:"a"}, "a")
]);

btn.onclick = function(){
    
    
  patch( vnode, vnode1)
}

insert image description here
We clicked to change the dom
insert image description here
to achieve the effect. Check the logic
for the first time to meet the conditions. 4. The old back and the new front are both p key is c.
First, do a detailed comparison between the old back and the new front
, and then execute insertBefore to insert the old back into the old Go to the front
and then add one to the old back and the new front to the back

Then the second time it came in, the old front did not change, and the new front logic was added to point to b. At this time, both the new front and the old front were b, which triggered the first one, and the next time, the two as were also the same
. Do the same logic processing before the first double

But there are still some logic loopholes and sometimes there will be an infinite loop. We will continue next time.

Guess you like

Origin blog.csdn.net/weixin_45966674/article/details/130916337