html5 key键值屏蔽,深入浅出 Vue 中的 key 值

5f366613990b0845.jpg

从前篇文章说起

前几天我写了一篇文章,sortable.js——Vue 数据更新问题 ,当时自己只是数据的强制刷新角度去分析,而且并没找到真正的“元凶”。

很感谢有人帮我指出,可能是 Vue 的 key 值,导致数据渲染不正确的。由此,我做了进一步的尝试。

key 的一个错误使用——使用 index 作为 key

不知道你在写 v-for 的时候,会不会直接使用 index 作为它的 key 值,是的,我承认我会,不得不说,这真的不是一个好习惯。

根据上篇文章,我们还是用 sortable.js 作为例子讨论。以下是核心代码,其中 arrData 的值为 [1,2,3,4]

{{item}}
mounted () {

let el = document.getElementById('sort')

var sortable = new Sortable(el, {

onEnd: (e) => {

const tempItem = this.arrData.splice(e.oldIndex, 1)[0]

this.arrData.splice(e.newIndex, 0, tempItem)

}

})

}

当然一开始的时候,数据渲染肯定是没有问题的

1597400288778704.png

好了,我们来看下以下的操作:157b7d87a27aadaf45ea97cfe0ca217c-1.gif

可以看到,我将3拖到2上面的时候,下面的数据变成了 1342,但是上面视图的还是1234。然后我第四位置拖到第三位置的时候,下面的数据也是生效的,但是上面的数据似乎全部错乱了。很好,我们重现了案发现场。

接着我改了绑定的 key 值,因为这里的例子比较特殊,我们就认为 item 的值都不相同

{{item}}

再看效果:9da08eb1750474c8a9585e7997554b27-2.gif

是的,这个时候数据就完全跟视图同步了。

为什么?

先看官方文档中 key 的一句介绍有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。

之所以会造成上面渲染错误的情况,是因为我们的 key 值不是独特的,比如上面的 key 值,在调整数组顺序后就每一项原来的 key 值都变了,所以导致了渲染错误。

我们先来得出一个结论,用 index 作为 key 值是有隐患的,除非你能保证 index 始终能够能够作为一个唯一的标识

key 值到底有什么用

在 vue2.0 之后,我们不写 key 的话,就会报 warning,那也就是说官方是希望我们写 key 值的,那么 key 到底在 vue 中扮演了什么样的角色?

不使用 key 可以提高性能么答案是,是的!可以!

先看官方解释:如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

比如现在有一个数组 [1,2,3,4]变成了[2,1,3,4],那么没有 key 的值会采取一种“就地更新策略”,见下图。它不会移动元素节点的位置,而是直接修改元素本身,这样就节省了一部分性能

1597400307965044.png

而对于有 key 值的元素,它的更新方式如下图所示。可以看到,这里它对 DOM 是移除/添加的操作,这是比较耗性能的。

1597400360861516.png

竟然不带 key 性能更优,为何还要带 key先来看一个例子,核心代码如下,这里模仿一个切换 tab 的功能,也就是切换的tab1 是1,2,3,4。tab2 是 5,6,7,8。其中有设置了一个点击设置第一项字体色为红色的功能。

那么当我们点击tab1将字体色设置成红色之后,再切换到 tab2,我们预期的结果是我们第一项字体的初始颜色而不是红色,但是结果却还是红色。

tab1

tab2

{{item}}
trunToTab1 () {

this.arrData = [1,2,3,4]

},

trunToTab2 () {

this.arrData = [5,6,7,8]

},

clickItem () {

document.getElementsByClassName('item')[0].style.color = 'red'

}209be3de939f87c63345c630db5fd7a1-5.gif

这就超出了我们的预期了,也就是官方文档所说的,默认模式指的就是不带 key 的状态,对于依赖于子组件状态或者临时 DOM 状态的,这种模式是不适用的。这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。

我们来看带上 key 之后的效果b9f515622aa56317f71ff972babfd0ba-6.gif

这就是官方文档之所以推荐我们写 key 的原因,根据文档的介绍,如下:使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

它也可以用于强制替换元素/组件而不是重复使用它。当你遇到如下场景的时候它可能会很有用:完整地触发组件的生命周期钩子

触发过渡

那么 Vue 底层 key 值到底是怎么去做到以上的功能?我们就得聊聊 diff 算法以及虚拟 DOM 了。

key 在 diff 算法中的作用

这里我们不谈 diff 算法的具体,只看 key 值在其中的作用。(diff 算法有机会我们再聊)

看 vue 源码中 src/core/vdom/patch.jsif (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)

idxInOld = isDef(newStartVnode.key)

? oldKeyToIdx[newStartVnode.key]

: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)

我们整理一下代码块:// 如果有带 key

if (isUndef(oldKeyToIdx)) {

// 创建 index 表

oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);

}

if (isDef(newStartVnode.key)) {

// 有 key ,直接从上面创建中获取

idxInOld = oldKeyToIdx[newStartVnode.key]

} else {

// 没有key, 调用 findIdxInOld

idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);

}

那么最主要还是 createKeyToOldIdx 和 findIdxInOld 两个函数的比较,那么他们做了什么呢?function createKeyToOldIdx (children, beginIdx, endIdx) {

let i, key

const map = {}

for (i = beginIdx; i <= endIdx; ++i) {

key = children[i].key

if (isDef(key)) map[key] = i

}

return map

}function findIdxInOld (node, oldCh, start, end) {

for (let i = start; i < end; i++) {

const c = oldCh[i]

if (isDef(c) && sameVnode(node, c)) return i

}

}

我们可以看到,如果我们有 key 值,我们就可以直接在 createKeyToOldIdx 方法中创建的 map 对象中根据我们的 key 值,直接找到相应的值。没有 key 值,则需要遍历才能拿到。相比于遍历,映射的速度会更快。

key 值是每一个 vnode 的唯一标识,依靠 key,我们可以更快的拿到 oldVnode 中相对应的节点。

参考本文转载自:https://juejin.im/post/6844903865930743815

作者:Gopal

想要了解更多相关知识,可访问 前端学习网站!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值