虚拟dom和diff算法--3(diff如何将虚拟节点转化为dom节点)

391 阅读6分钟

写在前面,该笔记来自于www.bilibili.com/video/BV1iX… 侵删

上节课的回顾

image.png 在源码中,init返回patch函数,所以我们从init.ts(看ts,写js)看起

image.png 首先patch函数接收老节点和新节点
然后判断oldVnode是不是虚拟节点,如果是就进行精细化比较(diff),如果不是就暴力删除旧的,再插入新的

image.png

image.png 邵山欢老师画的流程图:

image.png 而精细化比较是比较难的,后面再讲

如何定义"同一个节点"这个事儿

image.png 新老节点的选择器&&key值&&属性值?(ts我不会,老师讲课的时候没有data?.is,根据百度得知,和三目运算符有点类似,就是vnode1.data存在吗,存在的话看看中间is(is是啥没找到,但是data是存储属性的撒,大概就是说新老节点选择器和key值和属性值都要一样))

假设不是同一个节点,首先,得到父节点:

parent = api.parentNode(elm) as Node;//得到父节点

然后创建新节点:

createElm(vnode, insertedVnodeQueue);

创建新节点的代码特别多QAQ,使用到了递归/ 为啥要使用递归呢,还是使用上一节课的场景1:\

const vnode1 = h('ul', {key:'ul-ol'}, [

h('li', {key:'A'}, 'A'),

h('li', {key:'B'}, 'B'),

h('li', {key:'C'}, 'C'),

h('li', {key:'D'}, 'D'),

])

创建ul之后还得创建ul中的n个li呢,所以在这里使用到了递归
讲到这里发现之前有一个误解,container是容器,但是当虚拟节点上树的时候,虚拟节点是在container的位置上代替它,不是在这个容器的位置添加虚拟节点,container自己就没了

小总结1:在创建节点的时候,所有字节点都需要递归创建的

接下来来写一个patch的逻辑,这个逻辑一定要扎根于上面的流程图

在写创建新的节点(将虚拟节点转化为真实dom节点再上树),发现了以下问题,做一个记录:

// 真正创建节点,将vnode创建为dom,插入到pivot这个元素之前

export default function(vnode){

// export default function(vnode,pivot){

let domNode = document.createElement(vnode.sel)//创建节点,这个节点现在是孤儿节点,需要上树

// 有子节点还是文本

if(vnode.text != '' && vnode.children == undefined || vnode.children.length == 0){

// 就是说内部是文字

domNode.innerText = vnode.text

// 孤儿节点创建好了

// 上树,让标杆节点的父元素调用insertBefore方法

pivot.parentNode.insertBefore(domNode,pivot)

}else if(Array.isArray(vnode.children) && vnode.children.length > 0){

// 有节点嵌套下 ,(内部是子节点,就要进行递归)1.递归出口:vnode的最后一个参数是文本2.递归的时候要反复调用createElement函数,我们设定的参数除了vnode之外还有标杆pivot,递归的时候标杆不好设定,所以我们将标杆参数去掉。在去掉pivot之后,我们就将插入的动作放到patch.js中去做

\


}

}

就是说将creatElement.js只用来进行创建孤儿节点(包括节点内子节点中的各个节点的递归创建),不进行上树,上树的逻辑放到patch.js去做。下面是重写的createElement.js:

// 真正创建节点,将vnode创建为dom,是孤儿节点,不进行插入

export default function createElement(vnode){

console.log(vnode)

let domNode = document.createElement(vnode.sel)//创建节点,这个节点现在是孤儿节点

// 有子节点还是文本

if(vnode.text != '' && vnode.children == undefined || vnode.children.length == 0){

// 就是说内部是文字

domNode.innerText = vnode.text

// 孤儿节点创建好了

// 补充elm属性

}else if(Array.isArray(vnode.children) && vnode.children.length > 0){

// 递归

for(let i = 0;i<vnode.children.length;i++){

// 得到当前这一个children

let ch = vnode.children[i]

// 创建出它的dom,一旦调用createElement意味着创建出dom了,elm属性有值了,并且elm属性指向了创建出的dom但是还没有上树,是一个孤儿节点

let chDOM=createElement(ch)

// 上树

domNode.appendChild(chDOM)

}

vnode.elm=domNode

}

vnode.elm = domNode

return vnode.elm//返回elm,是真实的纯粹的dom对象

// dom上树有两种方法:insertBefore()和appendChild(),前者需要标杆,后者不需要

}

patch.js:

import vnode from './mySnabbdomm/vnode.js'

import createElement from './createElement.js'

export default function(oldVnode,newVnode){

// 判断第一个参数是dom节点还是虚拟节点

// 第一次上树的时候,oldVnode会是container,就是dom节点

if(oldVnode.sel == ''|| oldVnode.sel ==undefined){

// 说明是dom节点,此时就要将它包装为虚拟节点

oldVnode = vnode(oldVnode.tagName.toLowerCase(),{},[],undefined,oldVnode)//tagName:标签名

}

// 判断oldVnode和newVnode是不是同一个节点,怎么判断呢,判断它们的key是不是同样的

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

// 在截至2021.8.10日的最新版sunbbdom中,还包含到了新旧节点的属性值是否相同

console.log('是一个节点')

// createElement(newVnode,oldVnode.elm)

}else{

console.log('不是同一个节点,先插入新的,再暴力删除旧的')

// 为啥有这个顺序呢,因为如果先删除再插入的话,就找不到插入节点的标杆位置了

\


// 假设是第一次上树,那么oldVnode是container,它作为最后一个参数被包装成虚拟节点了。elm:这个虚拟dom对应的真实dom,undefined表示这个dom还没有上树

let newVnodeElm = createElement(newVnode)

// 在这里去进行插入到老节点之前,上树

if(oldVnode.elm && newVnodeElm){

oldVnode.elm.parentNode.insertBefore(newVnodeElm,oldVnode.elm);

// 一个父节点上有n个子节点,子节点是再createElement.js中完成上树的,使用的是递归appendChild()方法,循环结束以后返回一个包含了字节点的父节点,然后再在这里进行最后的上树,这个时候的上树使用的是insertBefore(),使用标杆上树

}
oldVnode.elm.parentNode.removeChild(oldVnode.elm)

// 这一步是在插入新节点之后对旧节点进行删除。

// 如果不删除,那么页面中的container是依然存在的,在删除之后,页面中的container才会消失。我们在前面进行过分析,我们的预期效果是不希望container依然存在的。

}

}

其实这里主要内容是讲如何将虚拟节点转化为dom节点的(是diff算法的一部分)

就是经过diff算法之后,对虚拟节点进行操作结束,如何将虚拟节点重新渲染(上树)到真实DOM节点上去
1.首先,有两种方式可以进行真实dom节点的创建:
    1).document.createElement()
    2).自定义(封装)函数createElement(),其实在这个函数中也是使用的方法1...
2.使用createElement.js得到的是孤儿节点有两种情况:
    1).只有节点本身,节点内部是文字,创建真实节点之后直接返回
    2).节点内还有子节点,在这里需要对内部的子节点进行递归创建,创建结束好后,将父节点带着子节点一块儿返回回去。在递归过程中,子节点上树到父节点使用的是appendChild()
3.在patch.js得到createElement.js返回的节点,进行最后一步上树(这一步也会最终显示到浏览器上来),在这里使用的上树方法是insertBefore(),使用这个方法进行上树需要注意的是要给一个标杆,准确上树。

ps.appendChild():向节点添加最后一个子节点;
insertBefore():在指定的已有子节点之前插入新的子节点。
目前为止做到这个步骤:

image.png