专栏首页前端之旅用 :key 管理可复用元素

用 :key 管理可复用元素

input 中的 key

我们先来看一个切换登录方式的例子:

<div v-if="isUser">
    <label>Login with account</label>
    <input type="text" placeholder="Enter your account">
</div>
<div v-else>
    <label>Login with email</label>
    <input type="text" placeholder="Enter your email">
</div>

<button @click="isUser=!isUser">click to toggle</button>

我们会发现,在点击按钮切换登录方式后,输入框中已有的内容没有被清除,这是为什么呢?

引用官方文档的原话:

Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。

这里的 input 实际上复用了切换之前的 input。而类似 <input><select><textarea> 这样的表单元素都有一个 internal state 保存着元素的值,在元素复用时,这个值是会得到保留的。

如果我们希望切换的时候不保留这个值呢?我们可以给两个 input 添加不同的 key。因为 Vue 是将 key 作为唯一标识从而来识别复用的元素的,如果两个元素的 key 不同,那么就相当于告诉 Vue “这两个元素是完全独立的,你不能用其中一个来复用另一个”。

接着再来看一个利用 v-for 生成 input 的例子。 假如我们的代码为:

<div id="app">
    <div v-for="(item,index) in array">
        {{item}}: <input type="text"> 
    </div>
</div>
const app = new Vue({
    el:'#app',
    data:{
        array:["A","B","C","D","E"]
    }
})

之后生成的 input 中我们填入字符串作为 internal state。如图:

在没有使用 key 的情况下,我们通过 app.array.splice(2,0,"F") 在 BC 之间插入 F,发现:

和之前一样,因为 Vue 采用的是 就地复用 策略,这意味着 ABCDE 在原地不动的情况下被复用了,CDE 都被重新渲染了一次,但先前的 internal state 仍然保留着。

出于性能考虑,有没有办法可以只移动个别元素,单独渲染要插入的那个新元素呢?有了前面的经验,我们会想到给每个 input 一个 key 值。

首先我们尝试将 index 作为 key,之后进行插入操作,发现:

问题依然存在。这是因为,我们将 index 作为复用的判断依据,相当于告诉 Vue:“只要这两个东西的 index 一样,就进行复用”。插入之前 C 的 index 是 2,插入之后 F 的 index 也是 2,于是 F 复用了 C,同理,DE 也被复用了,并因此重新渲染了一次。

index 是会随着插入删除改变的值,所以它实际上并不适合作为 key。于是我们想:在进行插入或者删除操作的时候,有没有一种值始终不会改变呢?有的,我们可以给每个元素一个单独的 id。但更简单的方法是直接使用 item,即元素本身的值,毕竟这个值对每个元素来说也是独一无二的。

我们将 item 作为 key ,之后进行插入操作,发现:

这回正常了。可以很明显地看到,每个元素都复用了先前的对应元素,这是因为此时 item (即元素值)才是复用的判断依据,相当于告诉 Vue:“只要这两个东西的元素值一样,就进行复用”。例如对于 C 来说,它只会复用与自己的值一样的元素,显然这个元素就是 C 本身。同理,D 复用 D,E 复用 E,CDE 都不需要重新渲染了,只需要后移以方便 F 插入,这时候的性能显然要好很多。

Virtual DOM 的 Diff 算法

下面大致从虚拟DOM的Diff算法实现的角度去解释一下。

vue 和 react的虚拟 DOM 的 Diff 算法大致相同,其核心是基于两个简单的假设:

  • 两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构。
  • 同一层级的一组节点,他们可以通过唯一的id进行区分。基于以上这两点假设,使得虚拟DOM的Diff算法的复杂度从O(n^3)降到了O(n)。

引用 React’s diff algorithm 中的例子:

当某一层有很多相同的节点时,也就是列表节点时,Diff 算法的更新过程默认情况下也是遵循以上原则。 比如一下这个情况:

我们希望可以在 B 和 C 之间加一个 F,Diff 算法默认执行起来是这样的:

即把 C 更新成 F,D 更新成 C,E 更新成 D,最后再插入 E,这样显然很没有效率。 所以我们需要使用 key 来给每个节点做一个唯一标识,Diff 算法就可以正确的识别此节点,找到正确的位置区插入新的节点。

所以 key 的作用主要是为了高效的更新虚拟 DOM。

参考: https://stackoverflow.com/questions/44077320/what-is-the-use-of-track-by-or-key-in-v-for-in-vue-js https://juejin.im/post/5aae19aa6fb9a028d4445d1a#comment

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 「译」一个案例搞懂 Vue.js 的作用域插槽

    作用域插槽是 Vue.js 中一个很有用的特性,可以显著提高组件的通用性和可复用性。问题在于,它实在不太好理解。尝试搞清楚父子作用域之间错综复杂的关系,其痛苦程...

    Chor
  • 关于dom对象和jq对象的疑问

    提示allp[i].attr()不是一个方法,而attr()是jq对象的方法,这个报错等于说allp[i]不是一个jq对象。 参考stackoverflow ,...

    Chor
  • 「译」利用 JavaScript 复制文本到剪贴板

    一个小小的诀窍。实现主题的复制代码功能时,思路刚好和这篇文章差不多,不过这篇文章的代码要更加合理。

    Chor
  • python call方法

    用户1733462
  • 在 Vue 中创建自定义输入

    基于组件的库或框架(如 Vue )可以创建 可重用组件 ,它能在各自应用程序中相互传递数据,这些框架能确保这些数据是一致的,并且(希望)简化了它们的使用方式。

    疯狂的技术宅
  • 12.19 生成ssl密钥对

    生成ssl密钥对目录概要 cd /usr/local/nginx/conf openssl genrsa -des3 -out tmp.key 2048//ke...

    运维小白
  • 开源 | CVPR2020 | 结构化的知识蒸馏算法,有效应用于语义分割、深度估计和目标检测

    论文地址:http://arxiv.org/pdf/1903.04197v5.pdf

    CNNer
  • 「R」t 检验

    你想要检验来自两个总体的样本是否有不同的均值(显著性差异),或者检验从一个总体抽取的样本均值和理论均值有显著性差异。

    王诗翔呀
  • 易日升金融总裁史建伟:对我们而言,大数据时代下的消费金融没有舒适区

    数据猿导读 易日升模式下的消费金融没有舒适区。我们做的是银行不愿做的累活苦活;我们做的是支付宝、京东金融没法做的个性细分场景的分期服务;我们做的是一般消费金融不...

    数据猿
  • 印度电力公司遭遇黑客攻击,勒索 1 RS Core 或 1000 万卢比

    上周,黑客攻占了印度 Uttar Haryana Bijli Vitran Nigam(UHBVN)电力公司的计算机系统,窃取了客户的账单数据。 攻击者对电力公...

    C4rpeDime

扫码关注云+社区

领取腾讯云代金券