从列表组件中key,延展到真实dom与虚拟dom

首先提出问题:
为何react,vue项目在组件列表中都推荐使用key呢?
为了详细解答此问题,需要延申到两个知识点:
① 虚拟DOM与真实DOM的工作逻辑
② diff算法

一、虚拟DOM与真实DOM的更新逻辑
首先要明确一点的是,虚拟DOM是一个表示真实DOM结构的轻量级副本,虚拟DOM确实不是实际的DOM节点,而是用来描述DOM结构的JavaScript对象。在虚拟DOM中,树状结构代表了UI的当前状态。这些对象包含了节点的类型、属性、子节点等信息,但它们并不直接与浏览器的DOM API进行交互。

更新的基本逻辑:
页面创建时=》
产生了真实DOM与虚拟DOM=》
数据发生变化时=》
创建了新的虚拟DOM(newVNode)=》
newVNode 与 旧的虚拟DOM(oldVNode) 使用diff算法做比较,生成变化集=》
真实DOM根据变化集更新自身。

在了解了上面基本逻辑后,我们可以大致的得出结论:在数据更新时,主要的操作都在新旧两个虚拟DOM中

一、diff算法
Diff算法的目的是通过比较两个虚拟DOM树来找出差异,然后将这些差异应用到真实DOM上。在此我们不深入讨论diff算法,只谈论其做了什么。

  1. 树的分层对比:将两棵树进行分层比较,只比较同一层级的节点,不跨层级比较。这极大地降低了比较的复杂度,从 O(n^3) 降低到 O(n)。
  2. 同层节点的比较:在同一层中,React和Vue分别采用不同的策略来优化比较。
    ①.React:React通过key属性来标记列表中的每个节点。通过比较key来判断节点是否相同。如果key相同,则认为是同一节点;如果key不同,则认为是不同的节点。
    ②.Vue也使用key属性来标识节点,但它还会在没有key的情况下,默认使用就地复用的策略,即默认会认为同一层级的相同位置的节点是相同的,除非显式地通过key进行区分。
  3. 节点的更新、删除和新增
    更新:如果新旧虚拟DOM的节点相同,则只更新其属性。
    删除:如果旧的虚拟DOM节点在新的虚拟DOM中不存在,则删除该节点。
    新增:如果新的虚拟DOM节点在旧的虚拟DOM中不存在,则新增该节点。
  4. 就地复用
    在Diff算法中,框架会尽量保持DOM节点的稳定性,即尽量复用现有的DOM节点,而不是删除旧节点再创建新节点。以下是具体的实现细节:
    ①.节点类型不变:
    如果新旧虚拟DOM节点的类型相同(例如都是div标签),则尝试复用现有的DOM节点,只更新必要的属性和内容。
    ②.Key属性:
    在列表中,Diff算法通过Key属性来标识节点。如果节点的Key属性相同,则视为同一节点,尝试复用现有的DOM节点。

在明白上面两个知识点后,再进行key值是否值得使用:
首先明确key的作用,key是给每一个vnode的唯一值,可以依靠key,更准确, 更快的拿到oldVnode中对应的vnode节点。

主要争论点来自于key值与就地复用

<div v-for="i in dataList" :key="i">{{ i }}</div>
dataList: [1, 2, 3, 4, 5]

以上的例子,v-for的内容会生成以下的dom节点数组,我们给每一个节点标记一个身份id:

  [
    '<div>1</div>', // id: A
    '<div>2</div>', // id:  B
    '<div>3</div>', // id:  C
    '<div>4</div>', // id:  D
    '<div>5</div>'  // id:  E
  ]

改变dataList数据,进行数据位置替换,对比改变后的数据

dataList = [4, 1, 3, 5, 2] // 数据位置替换
// 没有key的情况, 节点位置不变,但是节点innerText内容更新了
  [
    '<div>4</div>', // id: A
    '<div>1</div>', // id:  B
    '<div>3</div>', // id:  C
    '<div>5</div>', // id:  D
    '<div>2</div>'  // id:  E
  ]
  // 有key的情况,dom节点位置进行了交换,但是内容没有更新
  [
    '<div>4</div>', // id: D
    '<div>1</div>', // id:  A
    '<div>3</div>', // id:  C
    '<div>5</div>', // id:  E
    '<div>2</div>'  // id:  B
  ]

增删dataList列表项

  vm.dataList = [3, 4, 5, 6, 7] // 数据进行增删
  // 1. 没有key的情况, 节点位置不变,内容也更新了
  [
    '<div>3</div>', // id: A
    '<div>4</div>', // id:  B
    '<div>5</div>', // id:  C
    '<div>6</div>', // id:  D
    '<div>7</div>'  // id:  E
  ]
  // 2. 有key的情况, 节点删除了 A, B 节点,新增了 F, G 节点
  [
    '<div>3</div>', // id: C
    '<div>4</div>', // id:  D
    '<div>5</div>', // id:  E
    '<div>6</div>', // id:  F
    '<div>7</div>'  // id:  G
  ]

从以上来看,不带有key,并且使用简单的模板,基于这个前提下,可以更有效的复用节点,diff速度来看也是不带key更加快速的,因为带key在增删节点上有耗时。但是这种模式会带来一些隐藏的副作用,比如可能不会产生过渡效果,或者在某些节点有绑定数据(表单)状态,会出现状态错位。

总结:如果不是为了极限性能,以及在某些特定情况下,请务必加上key

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值