32-mini-vue 更新element的children-双端对比 diff 算法

更新element的children-双端对比 diff 算法

  1. 双端对比
  • 通过双端对比,把左边,右边缩小范围,将中间乱序的位置找出来
  • 找出来以后,
    • 老的有,新的没有,删除老的
    • 老的没有,新的有,创建新的
    • 老的,新的都有,但顺序发生改变,按照新的调整顺序
  1. 为什么进行双端对比?
  • 操作 dom 性能消耗大,比较昂贵,很多时候,新旧节点开头和结尾都是不动的,仅仅是中间位置进行了更改,双端对比减少了dom操作
  • 为了简化算法,遍历所有节点是 O(n) 的时间复杂度,双端对比,减少了 n
  1. 有哪些情况呢?
  • 仅双端对比部分
    • 左侧对比 左侧有相同的部分,比如: a b c < === > a b d e
    • 右侧对比 右侧有相同的部分,比如: a b c < === > d e b c
    • 新的比老的多
      • a b < === > a b c
      • c a b < === > a b
    • 老的比新的多
      • a b c < === > a b
      • a b c < === > b c
  • 中间对比
    • 老的有,新的没有,删除老的
    • 老的没有,新的有,创建新的
    • 老的,新的都有,但顺序发生改变,按照新的调整顺序
  1. 初始化文件
js 复制代码
// main.js index.html 省略
// App.js
import {
    h,
    ref
} from "../../lib/guide-mini-vue.esm.js";
import {
    ArrayToArray
} from "./ArrayToArray.js";
export const App = {
    name: "App",
    render() {
        return h("div", {},
            [
                h('p', {}, '主页'),
                h(ArrayToArray)
            ]
        )
    },
    setup() {
        return {}
    },
}
// ArrayToArray.js
import {
    h,
    ref
} from '../../lib/guide-mini-vue.esm.js'

// 1. 左侧的对比
//    (a b ) c
//     (a b )  d e
const preChildren = [
    h('p', {
        'key': 'A'
    }, 'A'),
    h('p', {
        'key': 'B'
    }, 'B'),
    h('p', {
        'key': 'C'
    }, 'C'),
]
const nextChildren = [
    h('p', {
        'key': 'A'
    }, 'A'),
    h('p', {
        'key': 'B'
    }, 'B'),
    h('p', {
        'key': 'D'
    }, 'D'),
    h('p', {
        'key': 'E'
    }, 'E'),
]
export const ArrayToArray = {
    name: "ArrayToArray",
    setup() {
        const isChange = ref(false);
        window.isChange = isChange;
        return {
            isChange,
        }
    },
    render() {
        const self = this
        return h('div', {}, self.isChange === false ? preChildren : nextChildren)
    }
}
  1. 双端对比实现
    1. 对比左侧
js 复制代码
// 这里对比的是  abc  ----  abde 
function patchChildren(n1, n2, container, parentComponent) {
  const { shapeFlag: prevShapeFlag, children: c1 } = n1
  const { shapeFlag: nextShapeFlag, children: c2 } = n2

  if (nextShapeFlag & shapeFlags.TEXT_CHILDREN) {
    // 这里的 TEXT_CHILDREN 这种情况,没有过多说明,即使没有说明,默认也包含这种情况,这块是优化完毕的代码 
    if (prevShapeFlag & shapeFlags.ARRAY_CHILDREN) {
      unmountChildren(n1.children)
    }
    if (c1 !== c2) {
      hostSetElementText(container, c2)
    }
  } else {
    // 这里是 转成了数组类型的结构       
    if (prevShapeFlag & shapeFlags.TEXT_CHILDREN) {
      hostSetElementText(container, "")
      mountChildren(c2, container, parentComponent)
    } else { // ✅ 其余位置上一章内容 
      // array to array
      patchKeyedChildren(c1, c2, container, parentComponent)
    }
  }
}

function isSameVNodeType(n1, n2) { // ✅  这里判断是否是同一个节点
  return n1.type === n2.type && n1.key === n2.key
}
function patchKeyedChildren(c1, c2, container,parentComponent) {  // ✅ 通过这几个下标来实现左侧对比范围划分
  let i = 0 // 左侧的初始下标
  let e1 = c1.length - 1 // 右侧的初始下标 (新节点)
  let e2 = c2.length - 1 // 右侧的初始下标 (旧节点)
  while (i <= e1 && i <= e2) { // 左侧下标始终小于右侧的下标
    if (isSameVNodeType(c1[i], c2[i])) {
      patch(c1, c2, container, parentComponent)
    } else {
      break;
    }
    i++
    }
  console.log(i);
}
  1. 对比右侧
js 复制代码
// 右侧的对比的数据
const preChildren = [
  h('p', { 'key': 'A' }, 'A'),
  h('p', { 'key': 'B' }, 'B'),
  h('p', { 'key': 'C' }, 'C'),
]
const nextChildren = [
  h('p', { 'key': 'D' }, 'D'),
  h('p', { 'key': 'E' }, 'E'),
  h('p', { 'key': 'B' }, 'B'),
  h('p', { 'key': 'C' }, 'C'),
]
  • 进行功能实现
js 复制代码
// 初始化时, 老节点第一个是 A,新节点第一个是 D,第一个值不一样,所以 i 直接为0,不再改变
while(i <= e1 && i <= e2) {
  let n1 = c1[e1]
  let n2 = c2[e2]

  if(isSameVNodeType(n1, n2)){
    patch(n1, n2, container, parentComponent)
  } else {
    break
  }
  e1--
  e2-- 
}
  1. 新的比老的多
  • 前后对比数据
js 复制代码
// a b  < === >  a b  c
const preChildren = [
  h('p', { 'key': 'A' }, 'A'),
  h('p', { 'key': 'B' }, 'B'),
]
const nextChildren = [
  h('p', { 'key': 'A' }, 'A'),
  h('p', { 'key': 'B' }, 'B'),
  h('p', { 'key': 'C' }, 'C'),
]
// c a b < === > a b
const preChildren = [
  h('p', { 'key': 'C' }, 'C'),
  h('p', { 'key': 'A' }, 'A'),S
  h('p', { 'key': 'B' }, 'B'),
]
const nextChildren = [
  h('p', { 'key': 'A' }, 'A'),
  h('p', { 'key': 'B' }, 'B'),
  h('p', { 'key': 'C' }, 'C'),
]
  • 具体的实现
js 复制代码
if(i > e1) {
  if(i <= e2) {
    const nextPos = i + 1
    const anchor =  nextPos < c2.length ? c2[nextPos].el : null
    while(i <= e2) { // 这里是说新节点比老节点多很多个,所以要逐个进行添加
      patch(null, c2[i], container, parentComponent, anchor)
      i++ 
    }
  }
}  
// run  time-dom/index.ts
export function insert(child, parent, ancher) { // ✅ 这里新增 ancher,所以涉及到 insert 方法的,如 patch 方法都增加了这个参数,很多位置,不做列举
  // parent.append(el)
  parent.insertBefore(child, ancher || null )
}
  1. 老的比新的多
  • 前后对比数据
js 复制代码
// a b c < === > a b
const preChildren = [
  h('p', { 'key': 'A' }, 'A'),
  h('p', { 'key': 'B' }, 'B'),
  h('p', { 'key': 'C' }, 'C'),
]
const nextChildren = [
  h('p', { 'key': 'A' }, 'A'),
  h('p', { 'key': 'B' }, 'B'),
]
// a b c < === > b c
const preChildren = [
  h('p', { 'key': 'A' }, 'A'),
  h('p', { 'key': 'B' }, 'B'),
  h('p', { 'key': 'C' }, 'C'),
]
const nextChildren = [
  h('p', { 'key': 'B' }, 'B'),
  h('p', { 'key': 'C' }, 'C'),
] 
  • 具体实现
js 复制代码
if (i > e1) {
// 省略 新的比老的多
} else if (i > e2) { // 老的比新的多
  while (i <= e1) {
    hostPatchRemove(c1[i].el)
    i++
  }
}
相关推荐
Huangichin2 小时前
C++期末复习
数据结构·c++·算法
写bug的可宋2 小时前
【Electron】解决Electron使用阿里iconfont不生效问题(react+vite)
javascript·react.js·electron
夏鹏今天学习了吗10 小时前
【LeetCode热题100(82/100)】单词拆分
算法·leetcode·职场和发展
mit6.82411 小时前
mysql exe
算法
2501_9011478311 小时前
动态规划在整除子集问题中的应用与高性能实现分析
算法·职场和发展·动态规划
中草药z11 小时前
【嵌入模型】概念、应用与两大 AI 开源社区(Hugging Face / 魔塔)
人工智能·算法·机器学习·数据集·向量·嵌入模型
知乎的哥廷根数学学派12 小时前
基于数据驱动的自适应正交小波基优化算法(Python)
开发语言·网络·人工智能·pytorch·python·深度学习·算法
ADI_OP12 小时前
ADAU1452的开发教程10:逻辑算法模块
算法·adi dsp中文资料·adi dsp·adi音频dsp·adi dsp开发教程·sigmadsp的开发详解
xingzhemengyou112 小时前
C语言 查找一个字符在字符串中第i次出现的位置
c语言·算法