为什么应该在 v-for 中使用 :key?
input
中的 key
引用 Vue 官方文档的原话:
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。
这样容易导致一些问题。因为类似
,
,
这样的元素都有一个 internal state
保存着元素的值,而在元素复用时,这个值是会得到保留的。
我们来看一个登陆方式切换的例子:
<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>
我们会发现,在点击按钮切换登录方式的时候,输入框中已有的内容 不会被清除
,这是因为 input
的 internal state
保留着元素的值。
如果我们希望切换的时候不保留这个值呢?我们可以给两个 input
添加不同的 :key
。因为 vue 是将 key
作为唯一标识从而来识别复用的元素的,如果两个元素的 key
不同,那么就相当于告诉 vue“这两个元素是完全独立的,不要复用它们”。
v-for
中的 key
同样的,使用 v-for
更新已渲染的元素列表时,默认用 就地复用
策略。列表数据修改的时候,vue 会根据 key
去判断某个值是否修改 —— 如果修改,则重新渲染这一项,否则复用之前的元素。在 v-for
中使用 key
是一个最佳实践,但是我们需要注意使用的是什么 key
。
假如我们想要在如下数组 [A,B,C,D,E]
的 B 和 C 之间插入 F:
index id Array.elem 0 1 A 1 2 B 2 3 C 3 4 D 4 5 E
-
如果是使用
index
作为key
:
在末尾插入 F 的话没有问题,因为这个时候不影响前面元素的 index,每个元素的 index 不变,而 vue 可以依据这些 index 对元素进行复用;但是现在是在中间插入 F,一旦插入成功,那么 CDE 的 index 都会改变,这时候 CDE 都需要重新渲染一次。而 AB 的 index 是不变的,所以 AB 可以得到复用。 -
如果是使用
id
作为key
:这里就要注意了,这个
id
是唯一的,也是固定不变的(也可以采用元素本身的值作为这个唯一的 id),不管插入还是删除,元素该是哪个id
还是哪个id
,这意味着以这样的id
作为key
时,所有旧元素都可以得到复用。所以这种情况下,我们只需要渲染新插入的 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。