diff的流程图
h函数,目的是创建出虚拟节点,然后准备上树
import vnode from './vnode.js'
// 低配版h函数,必须接受3个参数,作用于重载功能,调用的形态必须是下面的三种之一
// 形态1 h('div',{},'文字')
// 形态2 h('div',{},[])
// 形态3 h('div',{},h())
export default function(sel,data,c){
// 检查参数的个数
if(arguments.length!=3){
throw new Error('对不起,h函数必须传入三个参数,我们是低配版h函数')
}
//检查参数c的类型
if(typeof c=='string' || typeof c=='number'){
// 说明现在调用h寒素是形态1
return vnode(sel,data,undefined,c,undefined)
} else if(Array.isArray(c)){
let children = []
// 说明现在调用h函数是形态2
// 遍历
for(let i=0;i<c.length;i++){
// c[i]必须是一个对象,如果不满足
if(!(typeof c[i]=='object' && c[i].hasOwnProperty('sel'))) throw new Error('传入的数组参数中有不是h函数的项')
// 每一项都执行h函数,此时只需收集
children.push(c[i])
}
// children收集完毕返回虚拟dom
return vnode(sel,data,children,undefined,undefined)
} else if(typeof c=='object' && c.hasOwnProperty('sel')){
// 形态3
// 传入的c是唯一的数组
let children = [c]
return vnode(sel,data,children,undefined,undefined)
}else {
throw new Error('传入的第三个参数类型不对')
}
}
vnode.js
export default function(sel,data,children,text,elm){
return {
sel,data,children,text,elm
}
}
直接返回一个对象,包含如下:
sel 标签名,如div
data 一个对象,对象里头可以是key,可以是props属性,可以是class等等
children 子节点,children可以是文本,可以是数组,数组下还可以是虚拟dom
text 表示如div下的文本
elm 表示当前节点
path.js用于上树,第一个参数oldVnode是老节点,newVnode是即将上树的新节点
import vnode from "./vnode"
import createElement from "./createElement";
export default function patch(oldVnode,newVnode){
//判断传入的第一个参数是DOM节点还是虚拟节点
if(oldVnode.sel == '' || oldVnode.sel == undefined){
// 传入的第一个参数是DOM节点,此时包装为虚拟节点
oldVnode = vnode(oldVnode.tagName.toLowerCase(),{},[],undefined,oldVnode);
}
console.log(oldVnode);
// 判断oldVnode和newVnode是不是同一个节点
if(oldVnode.key==newVnode.key&& oldVnode.sel==newVnode.sel){
console.log("是同一个节点")
}else{
console.log("不是同一个节点,暴力插入新的,删除旧的")
let newVnodeElm = createElement(newVnode)
// 出入到老节点之前
if(oldVnode.elm.parentNode!=undefined && newVnodeElm){
oldVnode.elm.parentNode.insertBefore(newVnodeElm,oldVnode.elm);
}
console.log(oldVnode)
}
}
createElement.js 接收一个新的虚拟节点,里面包含sel,data,children,text,elm
目的是把虚拟节点变为真正的DOM节点
// 真正创建节点,将vnode创建为DOM,是孤儿节点,不进行插入
export default function createElement(vnode){
console.log('目的是把虚拟节点',vnode,'变为真正的DOM节点')
// 创建一个DOM节点,这个节点现在还是孤儿节点
let domNode = document.createElement(vnode.sel)
// 有子节点还是文本??
if(vnode.text!='' && (vnode.children==undefined || vnode.children.length==0)){
domNode.innerText = vnode.text;
}else if(Array.isArray(vnode.children) && vnode.children.length>0){
// 它内部是子节点,就要递归创建节点
for(let i=0;i<vnode.children.length;i++){
let ch = vnode.children[i]
// 创建出它的DOM,一旦调用createElement意味着:创建出DOM,并它的elm属性指向了创建出的DOM,但是还没有上树,是一个孤儿节点
let chDom = createElement(ch)
domNode.appendChild(chDom)
}
console.log(domNode)
}
vnode.elm = domNode
return vnode.elm
}
这里用到了递归,因为虚拟dom下还有子节点的虚拟DOM,就要不断的自我调用,将新的children下的子节点传入createElement下,然后将下一层的节点继续添加,最后返回这个总的DOM节点。
得到如下:
在旧节点前调用insertBefore就能成功上树。
index.js
import h from './mysnabbodm/h'
import patch from './mysnabbodm/patch'
// const myVnode = h('h1',{},'你好')
const myVnode1 = h('ul',{},[
h('li',{},'A'),
h('li',{},'B'),
h('li',{},[
h('div',{},'aaa')
]),
h('li',{},'D')
])
const container = document.getElementById('container')
patch(container,myVnode1)