上一文 我们整理了一下 patch 函数的整体过程
我不知道大家有没有保留我们之前学手写H函数时的那个案例 我们要将index.jsi还原成这样
参考代码如下
import h from "./snabbdom/h";
const dom1 = h("div",{
props: {class: "dom"}
},"文本测试");
const dom2 = h("div",{
props: {class: "dom"}
},[
h("div",{},"java"),
h("div",{},"html"),
h("div",{},[
h("div",{},"react"),
h("div",{},"css")
])
]);
const dom3 = h("div",{
props: {class: "dom"}
},h("div",{},"java"));
console.log(dom1);
console.log(dom2);
console.log(dom3);
只是说 我们还是要用自己写的那个h函数
但这次 我们真的是要把虚拟节点上树了
我们在案例的 src下的snabbdom 下创建一个 patch.js
可能有人会问 我们看到的不是init的吗?
没错 在源码中 patch 是init的一部分 init其实就是组装了各种模型 但我们只需要做一个简易版的patch 做一下虚拟节点上树 和 比较最小化更新就好了
然后 我们在案例 src下的snabbdom 下创建一个createElement.js
然后 我们先写patch.js的代码 便于大家理解
patch.js 参考代码如下
import vnode from "./vnode";
import createElement from "./createElement";
export default function(oldVnode, newVnode) {
//先判断oldVnode 第一个参数 是一个虚拟节点 还是一个真实的dom节点
if(!oldVnode.sel) {
//如果oldVnode中 拿不到sel 表示这是一个真实的节点 因为虚拟节点中 sel 代表标签类型选择器
//那么 我们就要将虚拟节点转为真实的节点
//这里 我们通过vnode 将真实节点转为真实节点
/*
第一个参数sel 标签选择器 我们通过tagName.toLowerCase() 获取dom节点的 标签类型
第二个参数data 标签的数据 dom老节点肯定是没有的 我们直接{}
第三个参数 children 子集集合 我们这里肯定是没有的 直接 [] 或者 undefined
第四个参数 text 文本内容 我们这里 没有直接undefined
最后一个 elm 他上树的节点 我们直接给他自己 oldVnode
*/
oldVnode = vnode(oldVnode.tagName.toLowerCase(),{},[],undefined,oldVnode);
}
//判断 oldVnode 和 newVnode 是不是同一个节点
//判断的条件是 sel 标签选择器 和 key 元素表示是否都相同
if(oldVnode.sel == newVnode.sel && oldVnode.key == newVnode.key) {
} else {
//else 走进来 表示两者并不是同一个节点
//那就执行 暴力拆除旧的 插入新的
createElement(newVnode, oldVnode.elm);
}
}
这里 我们就做了上文说的两件事 第一件 判断第一个节点是不是一个虚拟节点 如果不是 通过 vnode 转为虚拟节点 这里 我注释写的还是比较全的大家可以看一下
然后 判断 两个虚拟节点是不是一样的 如果是 那我们这篇文章先放在这 我妈后面再去补这块逻辑
然后 如果不是同一个节点
我们走进createElement 首先 我解释一下这两个参数 首先 第一个 是patch接到的第二个参数 就是要渲染上去的虚拟节点 然后 第二个 是 oldVnode.elm
那我们 上面调用vnode 最后一个参数对应elm 是不是 他这个elm存的就是 真实的dom节点本身啊
所以 第二个参数 我们是将这个存在elm中的真实的dom节点传进去了
好了 然后我们来写createElement.js的逻辑
//创建节点
//函数接收两个参数 vnode 需要插入的虚拟节点 pivot 需要插入到哪一个元素之前
export default function(vnode, pivot) {
//先用虚拟节点的sel 标签名选择器 创建一个真实的dom节点
let domNode = document.createElement(vnode.sel);
//如果 能判断到虚拟节点下的 text 文本属性 而且 拿不到children子节点 或者 children 子节点 没有长度
if(vnode.text && (!vnode.children || !vnode.children.length)){
//这个条件达到 表示 他的内容是一个文章 没有子节点
//直接将虚拟节点的 text 文本内容 通过innerText写入到新节点中
domNode.innerText = vnode.text;
/*
通过pivot上树 通过pivot.parentNode 获取到 pivot的父元素 放在insertBefore前 这个大家可以了解一下 insertBefore的概念
然后将刚刚通过虚拟节点创建的节点 通过insertBefore上树 惨遭就是pivot
*/
pivot.parentNode.insertBefore(domNode, pivot);
}
}
我们这里接到了这两个参数 第一个 要插入的虚拟节点 第二个 对照插入位置的一个真实dom节点
进来 我们先通过createElement 将虚拟节点 创建成一个真正的dom孤儿节点
createElement(标签名) 这里需要标签名 而我们虚拟节点的sel中是不是正好就是标签名?
然后 我们先判断 如果 这个节点的文本不是空的 而且 他没有子节点
那就 直接通过innerText 将虚拟节点中的文本属性text写进去
这样 我们参照插入的节点 和要插入的孤儿节点都好了 就直接执行
insertBefore上树
然后我们修改 src下 index.js 代码如下
import h from "./snabbdom/h";
import patch from "./snabbdom/patch";
const container = document.getElementById("container");
const vnode = h("h1", {}, "你好");
patch( container, vnode)
这里 我们先获取了页面上的 container元素 然后通过h函数创建了一个 h1虚拟节点
然后我们通过自己写的patch进行上树操作
运行代码如下
可以看到 方法虽然写的捡漏了一些 但中心思想没变 节点它还是上去了
然后 我们将src下 index.js 代码改一下 我们试试多层嵌套
import h from "./snabbdom/h";
import patch from "./snabbdom/patch";
const container = document.getElementById("container");
const vnode = h("ul", {}, [
h("li", {}, "1A"),
h("li", {}, "2B"),
h("li", {}, "3C"),
h("li", {}, "4D"),
]);
patch( container, vnode)
可以看到 这样 我们的节点直接就没了
这个 我们就需要利用递归的方式去处理一下了 下一文 我们再继续说