写在前面,该笔记来自于www.bilibili.com/video/BV1iX… 侵删
上节课的回顾
在源码中,init返回patch函数,所以我们从init.ts(看ts,写js)看起
首先patch函数接收老节点和新节点
然后判断oldVnode是不是虚拟节点,如果是就进行精细化比较(diff),如果不是就暴力删除旧的,再插入新的
邵山欢老师画的流程图:
而精细化比较是比较难的,后面再讲
如何定义"同一个节点"这个事儿
新老节点的选择器&&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():在指定的已有子节点之前插入新的子节点。
目前为止做到这个步骤: