Vue原理浅析--vnode结构编译为真实dom

478 阅读2分钟

vnode结构转换成真实dom

由于文章是本人在技术分享前准备的, 部分内容没有直接体现在文档上, 到最后大部分内容都是纯靠讲述了, 所以文章缺失了很多讲解和分析, 有空再来补上吧。

通过 snabbdom 学习vdom

github.com/snabbdom/sn…

在数据更新过程中的diff 比较

查看 h 函数

Patch 函数、patchVnode函数、updateChildren函数

流程图 (2).jpg

github.com/cujojs/jiff

流程图 (3).jpg

diff算法的特点

时间复杂度从 O(n^3 ) 到 O(n)。

O(n^3 ) 的话,1000个节点就要计算1亿次,算法不可用

  • 只比较同级节点,不跨级比较
  • Tag 不相同,则直接删掉重建,不再深度比较
  • Tag 和 key 两者都相同,则认为是相同节点,则向下比较

6,示例:不加正确的key会产生哪些问题?

      
<template>
  <div id="app">
    <div v-for="(item, index) in data" :key="index">
      <Child :key="index"/>
      <button @click="handleDelete(index)">删除这一行</button>
    </div>
  </div>
</template>

<script>
import Child from "./Child";
export default {
  name: "App",
  components: {
    Child
  },
  data() {
    return {
      data: [
        { name: "小明" },
        { name: "小红" },
        { name: "小蓝" },
        { name: "小紫" },
      ]
    };
  },
  methods: {
    handleDelete(index) {
      this.data.splice(index, 1);
    },
  }
};
</script>

      
<template>
  <span>{{name}}{{Math.floor(Math.random() * 1000)}}</span>
</template>

<script>
export default {
  name: "Child",
  props: {
    name: {
      type: String,
      default: ''
    }
  }
}
</script>
    

模拟上述template的dom结构

[
  {
    tag: 'div',
    key: 0,
    children: [
      {
        tag: VueComponent,
        elm: 408, // 这个Vnode对应的真实dom是408
      },
      {
        tag: 'button'
      }
    ]
  },
  {
    tag: 'div',
    key: 1,
    children: [
      {
        tag: VueComponent,
        elm: 227, // 这个Vnode对应的真实dom是227
      },
      {
        tag: 'button'
      }
    ]
  }
  ...
]

删除第一条数据后,新的dom结构为

[
  {
    tag: 'div',
    key: 0,
    children: [
      {
        tag: VueComponent,
        elm: 227, // 这个Vnode对应的真实dom是227
      },
      {
        tag: 'button'
      }
    ]
  },
  {
    tag: 'div',
    key: 1,
    children: [
      {
        tag: VueComponent,
        elm: 324, // 这个Vnode对应的真实dom是324
      },
      {
        tag: 'button'
      }
    ]
  }
  ...
]

由于 key 都是0,所以比较第一条的时候,就会命中 sameNode ,导致错误复用, 然后 updateChildren ,子节点的 Vnode 依然会命中 sameVnode , 同理,第二、三条均会命中 sameVnode ,而直接错误复用其关联的真实 dom 节点, 所以我们明明删除的是第一条,UI表现却是最后一条被删除了。

扩展思考:为什么日常用 index 作为 key 时没出现问题?

这次, 我们尝试将name属性传入其中, 删除第二条

<template>
    <div id="app">
        <div v-for="(item, index) in data" :key="index">
            <Child :name="`${item.name}`" />
            <button @click="handleDelete(index)">删除这一行</button>
        </div>
    </div>
</template>

因为key 一致,新的 Vnode 数组依然会复用旧的 Vnode 数组的前三条, 第一条 Vnode 是正确复用,组件的 propsData 未发生变化,不会触发 update , 直接复用其关联的真实 dom 节点,但是第二条 Vnode 是错误复用,但是组件的 propsData 发生变化, 由小红变成了小蓝,触发了 update ,组件重新渲染, 因此我们看到其实连 random 都发生了变化,第三条同理。