为什么要在Vue.js中使用v-for的key指令?

97 阅读6分钟

在任何网络或移动应用程序中,我们每天都会看到列表。所有那些我们不断滚动的Facebook、Twitter和Instagram feed - 它们都是某种形式的列表。

在Vue中实现列表就像小菜一碟,这要感谢v-for 指令。它可以让你对数组和对象进行迭代,并以你想要的方式表示每个元素。

v-for over arrays

这里有一个Vue中非常基本的列表渲染的例子。

虽然这很直接,但在某些情况下,你会在一切看起来都很正常的情况下抓耳挠腮。如果你不把:key 指令和v-for 指令一起使用,就可能发生这种情况。在我进一步解释之前,让我澄清一些事情,这将有助于你更好地理解这个错误和它的解决方案。

应用程序状态和DOM状态

为了理解这个错误,我们需要知道应用程序状态和临时DOM状态之间的区别。让我们看一个例子。

在这里,我创建了一个带有输入框的推文列表,你可以对推文进行评论(评论框是为了说明问题而创建的假象)。

在上面的例子中,一个tweet-box 组件正在从提供给该组件的推文数组的数据中显示其信息。这一部分是直接从应用状态(tweets数组)渲染数据。

组件的另一部分tweet-box 是一个输入框,它不与Vue实例中的任何数据相联系,因此独立于应用状态。所以,如果你在评论框中输入一些东西,并把它放在那里,你刚刚输入的文字就会被暂时存储到DOM状态中。

DOM state and application state

就地修补策略

到现在为止,我们已经看到了Vue.js中简单的渲染列表,也看到了应用状态和DOM状态之间的区别。现在,我们需要了解Vue用来渲染列表的 "就地修补策略 " 的东西。

Vue的文档描述道:"如果数据项的顺序发生了变化,Vue不会移动DOM元素以匹配项目的顺序,而是就地修补每个元素,并确保它反映了在该特定索引处应该呈现的内容"。让我们深入了解一下。

Vue以这样的方式渲染列表,每当UI中出现动态变化时,例如,列表的顺序发生变化,Vue将只是更新该列表中每个元素的DOM中的数据。Vue不会移动该列表中每个元素的HTML块,而只是通过更新DOM元素的状态,将当前应用状态更新到DOM中。

In place patch strategy

这表明Vue更倾向于尽可能多地重用DOM,并尝试在数据层面而非DOM层面进行操作。这种方式提高了Vue的整体性能,但是,当用户界面发生动态变化,并且DOM元素的数据依赖于子组件或临时DOM状态时,这可能会导致问题。这种行为将在下一节中得到很好的解释。

更新元素的顺序

现在我们知道,如果列表元素的顺序有变化,那么Vue会选择改变这些元素的数据,而不是移动这些DOM元素的顺序。

所以,为了看到这个动作,我在前面的Tweets例子中添加了一个Shuffle按钮。每当我点击shuffle按钮,推文的顺序就会改变,就像我们说的那样。你可以在这里自己尝试一下。

但在这里,我希望你能看到一些东西,以便理解我们之前谈到的问题。

让我们在任何一条推文的评论框中写一个评论,然后点击Shuffle按钮。

Shuffle button for changing order of tweets

你可以看到,我为某条推文写的评论不会根据推文的洗牌情况而移动。

为什么会发生这种情况?

快速回答:原地修补策略。

评论属于临时的DOM状态,这基本上意味着它属于DOM,并没有绑定到Vue所识别的任何数据。我们已经知道,当Vue中元素的顺序发生变化时,DOM中只有这些元素的数据发生变化,而不是DOM元素本身。实际上,你可以在这里看到这种情况发生。

为了更好地解释这个场景,假设我们在一个列表中有两个元素,X和Y,这些元素在它们的临时DOM状态中都有一些数据。现在,如果我们改变X和Y的顺序,那么这些变化将只反映在DOM中与X和Y绑定的部分。

Elements x and y without in-place patch

这是因为Vue没有办法将一个元素的DOM状态中的任何内容与该元素的应用状态绑定。

所以,让我们看看如何解决这个问题。

解决方案(使用:key 属性)

解决这种问题的唯一办法是像这样将:key 属性与v-for

Tweet box component

我们必须给列表中的每个元素分配一个某种唯一的ID。在这种情况下,我们为每条tweet分配一些id,而且是唯一的。这里是同样的推文例子,在应用了:key 属性之后。

让我们试着写一个评论,再洗一遍。

Shuffle button for changing order of tweets with solution

现在,你可以清楚地看到,在洗牌时,为某条推文写的评论是坚持的,即使顺序改变。

那么,这里发生了什么不同的情况?

让我用同样的X和Y元素的例子来解释。所以,如果我们给这些X和Y元素分配唯一的ID(使用:key 属性),Vue就会开始识别每个元素和它的DOM状态,将其作为自己的唯一实例。这基本上意味着,现在无论X和Y的临时DOM状态是什么,都与X和Y的应用状态绑定。

所以,现在如果我们调换X和Y的顺序,Vue看到的是Vue通过这些唯一的id识别的唯一元素的调换。

X and Y elements with in-place patch strategy

结论

现在看来,这可能不是什么大问题,但试想一下,你在你的Vue应用中渲染列表,事情变得非常复杂。然后,如果你开始看到奇怪的行为发生,就很难确定是什么地方出了问题,即使解决方案就像添加一个:key 属性一样简单。

这就是为什么我们总是建议在使用:key 属性的同时使用v-for 。几乎在所有的情况下,如果你不使用:key ,你不会看到任何问题发生,但在某些时候,如果列表取决于DOM状态和/或子组件的状态,那么这可能导致一些有趣的行为发生。