2018-01-31
Virtual-DOM
diff 和 patch 操作
完成了 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 的简单实现:
  1. [√] 利用 JavaScript 表达 DOM 的树形结构
  2. [√] 根据真实的 DOM 树生成虚拟 DOM
  3. [√] 根据虚拟 DOM 生成真实的 DOM 树
  4. [√] 自己实现了一个简单的 diff 算法
  5. [√] 节点的 diff 操作
  6. [√] 节点的 patch 操作
  7. [√] 将 patch 操作应用到真实的 DOM 上
收获很大,学到了很多东西。




回到顶部