26-更新element-children-1

132 阅读6分钟

更新对比

children更新主要考虑一下3种情况,其中array->array是最复杂的,这章节先做其他两种情况

  1. text -> array
  2. array -> array
  3. array -> text

例子

App.js

/*
 * @Author: Lin ZeFan
 * @Date: 2022-04-04 11:44:08
 * @LastEditTime: 2022-04-04 11:46:34
 * @LastEditors: Lin ZeFan
 * @Description:
 * @FilePath: \mini-vue3\example\patchChildren\App.js
 *
 */

import { h } from "../../lib/mini-vue.esm.js";
import ArrayToText from "./ArrayToText.js";
import TextToNewText from "./TextToNewText.js";
import TextToArray from "./TextToArray.js";
import ArrayToArray from "./ArrayToArray.js";

export default {
  setup() {
    return {};
  },
  render() {
    return h("div", {}, [
      h("div", {}, "3种情况变化"),
      // h(ArrayToText),
      // h(TextToNewText),
      // h(TextToArray),
      h(ArrayToArray),
    ]);
  },
};

main.js

/*
 * @Author: Lin ZeFan
 * @Date: 2022-04-04 11:51:55
 * @LastEditTime: 2022-04-04 11:51:56
 * @LastEditors: Lin ZeFan
 * @Description: 
 * @FilePath: \mini-vue3\example\patchChildren\main.js
 * 
 */


import App from './App.js'
import { createApp } from '../../lib/mini-vue.esm.js'

createApp(App).mount('#app')

ArrayToText.js

/*
 * @Author: Lin ZeFan
 * @Date: 2022-04-04 11:44:36
 * @LastEditTime: 2022-04-04 11:46:23
 * @LastEditors: Lin ZeFan
 * @Description: 
 * @FilePath: \mini-vue3\example\patchChildren\ArrayToText.js
 * 
 */

import { h, ref } from '../../lib/mini-vue.esm.js'

export default {
  setup() {
    const isChange = ref(false)
    window.isChange = isChange
    return {
      isChange,
    }
  },
  render() {
    return h(
      'div',
      {},
      this.isChange
        ? 'newChildren'
        : [h('div', {}, 'oldChildren1'), h('div', {}, 'oldChildren1')]
    )
  },
}

TextToArray.js

/*
 * @Author: Lin ZeFan
 * @Date: 2022-04-04 11:44:45
 * @LastEditTime: 2022-04-04 11:46:48
 * @LastEditors: Lin ZeFan
 * @Description: 
 * @FilePath: \mini-vue3\example\patchChildren\TextToArray.js
 * 
 */

import { h, ref } from '../../lib/mini-vue.esm.js'

export default {
  setup() {
    const isChange = ref(false)
    window.isChange = isChange
    return {
      isChange,
    }
  },
  render() {
    return h(
      'div',
      {},
      this.isChange
        ? [h('div', {}, 'newChildren1'), h('div', {}, 'newChildren2')]
        : 'oldChildren'
    )
  },
}

TextToNewText.js

/*
 * @Author: Lin ZeFan
 * @Date: 2022-04-04 11:44:41
 * @LastEditTime: 2022-04-04 11:47:02
 * @LastEditors: Lin ZeFan
 * @Description: 
 * @FilePath: \mini-vue3\example\patchChildren\TextToNewText.js
 * 
 */

import { h, ref } from '../../lib/mini-vue.esm.js'

export default {
  setup() {
    const isChange = ref(false)
    window.isChange = isChange
    return {
      isChange,
    }
  },
  render() {
    return h('div', {}, this.isChange ? 'newChildren' : 'oldChildren')
  },
}

ArrayToArray.js

/*
 * @Author: Lin ZeFan
 * @Date: 2022-04-04 11:44:52
 * @LastEditTime: 2022-04-04 11:44:52
 * @LastEditors: Lin ZeFan
 * @Description:
 * @FilePath: \mini-vue3\example\patchChildren\ArrayToArray.js
 *
 */

// 老的是 array
// 新的是 array

import { ref, h } from "../../lib/mini-vue.esm.js";

// 1. 左侧的对比
// (a b) c
// (a b) d e
// const prevChildren = [
//   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"),
// ];

// 2. 右侧的对比
// a (b c)
// d e (b c)
// const prevChildren = [
//   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"),
// ];

// 3. 新的比老的长
//     创建新的
// 左侧
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// const prevChildren = [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"),
// ];

// 右侧
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
// const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
// const nextChildren = [
//   h("p", { key: "C" }, "C"),
//   h("p", { key: "A" }, "A"),
//   h("p", { key: "B" }, "B"),
// ];

// 4. 老的比新的长
//     删除老的
// 左侧
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1
// const prevChildren = [
//   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)
// i = 0, e1 = 0, e2 = -1

// const prevChildren = [
//   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")];

// 5. 对比中间的部分
// 删除老的  (在老的里面存在,新的里面不存在)
// 5.1
// a,b,(c,d),f,g
// a,b,(e,c),f,g
// D 节点在新的里面是没有的 - 需要删除掉
// C 节点 props 也发生了变化

// const prevChildren = [
//   h("p", { key: "A" }, "A"),
//   h("p", { key: "B" }, "B"),
//   h("p", { key: "C", id: "c-prev" }, "C"),
//   h("p", { key: "D" }, "D"),
//   h("p", { key: "F" }, "F"),
//   h("p", { key: "G" }, "G"),
// ];

// const nextChildren = [
//   h("p", { key: "A" }, "A"),
//   h("p", { key: "B" }, "B"),
//   h("p", { key: "E" }, "E"),
//   h("p", { key: "C", id: "c-next" }, "C"),
//   h("p", { key: "F" }, "F"),
//   h("p", { key: "G" }, "G"),
// ];

// 5.1.1
// a,b,(c,e,d),f,g
// a,b,(e,c),f,g
// 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑)
// const prevChildren = [
//   h("p", { key: "A" }, "A"),
//   h("p", { key: "B" }, "B"),
//   h("p", { key: "C", id: "c-prev" }, "C"),
//   h("p", { key: "E" }, "E"),
//   h("p", { key: "D" }, "D"),
//   h("p", { key: "F" }, "F"),
//   h("p", { key: "G" }, "G"),
// ];

// const nextChildren = [
//   h("p", { key: "A" }, "A"),
//   h("p", { key: "B" }, "B"),
//   h("p", { key: "E" }, "E"),
//   h("p", { key: "C", id:"c-next" }, "C"),
//   h("p", { key: "F" }, "F"),
//   h("p", { key: "G" }, "G"),
// ];

// 2 移动 (节点存在于新的和老的里面,但是位置变了)

// 2.1
// a,b,(c,d,e),f,g
// a,b,(e,c,d),f,g
// 最长子序列: [1,2]

// const prevChildren = [
//   h("p", { key: "A" }, "A"),
//   h("p", { key: "B" }, "B"),
//   h("p", { key: "C" }, "C"),
//   h("p", { key: "D" }, "D"),
//   h("p", { key: "E" }, "E"),
//   h("p", { key: "F" }, "F"),
//   h("p", { key: "G" }, "G"),
// ];

// const nextChildren = [
//   h("p", { key: "A" }, "A"),
//   h("p", { key: "B" }, "B"),
//   h("p", { key: "E" }, "E"),
//   h("p", { key: "C" }, "C"),
//   h("p", { key: "D" }, "D"),
//   h("p", { key: "F" }, "F"),
//   h("p", { key: "G" }, "G"),
// ];

// 2.2
// a,b,(c,d,e,z),f,g
// a,b,(d,c,y,e),f,g
// 最长子序列: [1,3]

// const prevChildren = [
//   h("p", { key: "A" }, "A"),
//   h("p", { key: "B" }, "B"),
//   h("p", { key: "C" }, "C"),
//   h("p", { key: "D" }, "D"),
//   h("p", { key: "E" }, "E"),
//   h("p", { key: "Z" }, "Z"),
//   h("p", { key: "F" }, "F"),
//   h("p", { key: "G" }, "G"),
// ];

// const nextChildren = [
//   h("p", { key: "A" }, "A"),
//   h("p", { key: "B" }, "B"),
//   h("p", { key: "D" }, "D"),
//   h("p", { key: "C" }, "C"),
//   h("p", { key: "Y" }, "Y"),
//   h("p", { key: "E" }, "E"),
//   h("p", { key: "F" }, "F"),
//   h("p", { key: "G" }, "G"),
// ];

// 3. 创建新的节点
// a,b,(c,e),f,g
// a,b,(e,c,d),f,g
// d 节点在老的节点中不存在,新的里面存在,所以需要创建
// const prevChildren = [
//   h("p", { key: "A" }, "A"),
//   h("p", { key: "B" }, "B"),
//   h("p", { key: "C" }, "C"),
//   h("p", { key: "E" }, "E"),
//   h("p", { key: "F" }, "F"),
//   h("p", { key: "G" }, "G"),
// ];

// const nextChildren = [
//   h("p", { key: "A" }, "A"),
//   h("p", { key: "B" }, "B"),
//   h("p", { key: "E" }, "E"),
//   h("p", { key: "C" }, "C"),
//   h("p", { key: "D" }, "D"),
//   h("p", { key: "F" }, "F"),
//   h("p", { key: "G" }, "G"),
// ];

export default {
  name: "ArrayToArray",
  setup() {
    const isChange = ref(false);
    window.isChange = isChange;

    return {
      isChange,
    };
  },
  render() {
    const self = this;
    return h("div", {}, self.isChange === true ? nextChildren : prevChildren);
  },
};

实现

text 转 text

新老children都是text,直接设置元素的text即可

新增一个 setElementText 方法

runtime-core\render.ts

 export function createRenderer(options) {
  // 改名字是为了 debug 方便
  const {
    createElement: hostCreateElement,
    insert: hostInsert,
    patchProp: hostPatchProp,
    selector: hostSelector,
    setElementText: hostSetElementText,
  } = options;
  
  // other code

runtime-dom\index.ts

export function setElementText(el, text) {
  el.textContent = text;
}

const renderer: any = createRenderer({
  createElement,
  patchProp,
  insert,
  selector,
  setElementText,
});

判断children类型

 function patchChildren(
    n1: any,
    n2: any,
    container: any,
    parentComponent: any
  ) {
    const { el, children: n1Children } = n1;
    const { children: n2Children } = n2;
    const n1ShapeFlags = getChildrenShapeFlags(n1Children);
    const n2ShapeFlags = getChildrenShapeFlags(n2Children);

    if (n1ShapeFlags === ShapeFlags.TEXT_CHILDREN) {
      if (n2ShapeFlags === ShapeFlags.TEXT_CHILDREN) {
        // text -> text
        // 直接覆盖值
        hostSetElementText(el, n2Children);
      } 
    }
  }

text 转 array

新的children是array的话,把老的text先清空,再把新的array循环push进去

 function patchChildren(
    n1: any,
    n2: any,
    container: any,
    parentComponent: any
  ) {
    const { el, children: n1Children } = n1;
    const { children: n2Children } = n2;
    const n1ShapeFlags = getChildrenShapeFlags(n1Children);
    const n2ShapeFlags = getChildrenShapeFlags(n2Children);

    if (n1ShapeFlags === ShapeFlags.TEXT_CHILDREN) {
      if (n2ShapeFlags === ShapeFlags.TEXT_CHILDREN) {
        // text -> text
        // 直接覆盖值
        hostSetElementText(el, n2Children);
      } else {
        /** text -> array
         * 1. 先清空原先的text
         * 2. 再push进新的children
         */
        hostSetElementText(el, "");
        mountChildren(n2Children, el, parentComponent);
      }
    }

array 转 text

先做子元素删除,再设置textContent

新增remove函数

runtime-dom\index.ts

export function remove(child) {
  const parentNode = child.parentNode;
  parentNode && parentNode.removeChild(child);
}

const renderer: any = createRenderer({
  createElement,
  patchProp,
  insert,
  selector,
  setElementText,
  remove,
});

循环删除子元素

runtime-core\render.ts

function patchChildren(
    n1: any,
    n2: any,
    container: any,
    parentComponent: any
  ) {
    const { el, children: n1Children } = n1;
    const { children: n2Children } = n2;
    const n1ShapeFlags = getChildrenShapeFlags(n1Children);
    const n2ShapeFlags = getChildrenShapeFlags(n2Children);

    if (n1ShapeFlags === ShapeFlags.TEXT_CHILDREN) {
      if (n2ShapeFlags === ShapeFlags.TEXT_CHILDREN) {
        // text -> text
        // 直接覆盖值
        hostSetElementText(el, n2Children);
      } else {
        /** text -> array
         * 1. 先清空原先的text
         * 2. 再push进新的children
         */
        hostSetElementText(el, "");
        mountChildren(n2Children, el, parentComponent);
      }
    } else {
      if (n2ShapeFlags === ShapeFlags.TEXT_CHILDREN) {
        /** array -> text
         * 1. 删除子元素
         * 2. 重新赋值
         */
        unmountChildren(n1Children);
        hostSetElementText(el, n2Children);
      }
    }
  }
  
  function unmountChildren(child) {
    for (let index = 0; index < child.length; index++) {
      // 是一个h对象,当前dom元素存在el
      const element = child[index] && child[index].el;
      hostRemove(element);
    }
  }

array 转 array 实现

array 转 array 实现