完成了 diff 算法,虚拟 DOM 几乎就已经完成了,本篇接续上篇的 《diff 算法》 来谈 VNode 的 diff 和 patch 操作,以及如何将 patch 应用到真实的 DOM 上。
# VNode 的 diff ↵
一般来说,两个 VNode 节点不必完全深度的 diff, 而仅需 diff 一层就足够了,除了 diff 子代的变化,还应该 diff 属性的变化。
00// diff.js 01const VNode = require('./VNode') 02 , list_diff = require('./list-diff') 03 , { INSERT, REORDER, DELETE, Patches } = list_diff 04 05// Export 06module.exports = diff; 07 08/** 09 * @description diff t1 and t2 10 * @param { VNode } t1 源树 11 * @param { VNode } t2 目标树 12 * @returns { TreePatch } diff 结果 13 */ 14function diff(t1, t2){ 15 let strTable = {} 16 17 if (!t1 || !t2) return null; 18 19 let childrenDiff = list_diff(t1.children, t2.children, 'vid'); 20 21 let propDiff = {}; 22 Object.keys(t1.props).forEach(t1_prop => { 23 let t1_val = t1.props[t1_prop]; 24 let t2_val = t2.props[t1_prop]; 25 if (t1_val != t2_val) { 26 propDiff[t1_prop] = t2_val; 27 } 28 }); 29 30 return { 31 childrenDiff, propDiff 32 }; 33}
以上操作会产生 childrenDiff 和 propDiff,如下方法可以 patch :
00/** 01 * @description 把 diff 结果应用到 t1 上 02 * @param { VNode } t1 03 * @param { TreePatch } Patches 04 */ 05diff.patch = (t1, tree_patch) => { 06 let { childrenDiff, propDiff } = tree_patch; 07 08 let { children } = t1; 09 10 // prop patch 11 Object.keys(propDiff).forEach(key => { 12 let newVal = propDiff[key]; 13 t1.props[key] = newVal; 14 }) 15 16 // children patch 17 childrenDiff.to(children); 18}
主要是根据
type
进行数组操作,这样就可以完成虚拟 DOM 的 diff 操作了。# 真实 DOM 的 patch 操作 ↵
从以上的实现中我们可以实现 VNode 的 diff 和 patch 操作,接下来是吧 patch 操作应用到 DOM 上。
00/** 01 * @description patch 2 dom 02 * @param { tree_patch } diffRes diff 结果 03 */ 04VNode.prototype.$patch = function(tree_patch){ 05 let { childrenDiff, propDiff } = tree_patch; 06 let $ = this.$$(); 07 08 // 属性的 Diff 09 Object.keys(propDiff).forEach(key => { 10 let newVal = propDiff[key]; 11 $.setAttribute(key, newVal); 12 }); 13 14 // 子代 Diff 15 let children = this.children; 16 let $children = $.childNodes; 17 18 childrenDiff.forEach(patch => { 19 let { idx, item, type } = patch; 20 let $ref = $children[idx]; 21 22 // INSERT, REORDER, DELETE 23 if (type === DELETE) { // 删除 24 $.removeChild($ref); 25 } else if (type === INSERT) { // 插入 26 // console.log(item.$$(), $ref); 27 $.insertBefore( 28 item.$$(), 29 $ref 30 ); 31 } else { // REORDER 交换 32 let $temp = $children[item]; 33 let $next = $temp.nextSibling; 34 35 $.insertBefore($temp, $ref); 36 $.insertBefore($ref, $next); 37 } 38 }); 39}
利用 removeChild 来处理
DELETE
, 利用 insertBefore 处理 INSERT
。 至于交换顺序 REORDER 则使用了两次 insertBefore
来做。第一次 insertBefore 的时候,因为
temp 的下一个节点了,此外,如果
temp 已经是最后一个节点的时候,
ref,
.appendChild($ref)
# TL;DR ↵
至此,完成了一个虚拟 DOM 的简单实现:
- [√] 利用 JavaScript 表达 DOM 的树形结构
- [√] 根据真实的 DOM 树生成虚拟 DOM
- [√] 根据虚拟 DOM 生成真实的 DOM 树
- [√] 自己实现了一个简单的 diff 算法
- [√] 节点的 diff 操作
- [√] 节点的 patch 操作
- [√] 将 patch 操作应用到真实的 DOM 上
收获很大,学到了很多东西。