特别声明:此篇文章内容来源于@HASSAN DJIRDEH的《List Rendering and Vue’s v-for Directive》一文。
Web渲染是Web开发中最常用的实战之一。动态列表渲染通常用于简洁友好的格式向用户渲染一系列相似的分组信息。在我们使用的每个Web应用程序中,都可以看到很多内容列表被用于Web应用程序当中。
在这篇文章中,我们将收集有关于Vue中的v-for
指令生生动态列表的理解,并通过一些示例来说明为什么在这样做的时候应该使用key
属性。
由于我们将在开始编写代码时全面地解释一些事情,本文假设你对Vue或其他JavaScript框架有一定的了解。
案例:Twitter
我将使用 Twitter作为本文的案例。
当登录了Twitter并进入其首页时,我们会看到一个类似下图的一个效果:
在首页上,我们可以看到面页包括了你的趋势,信息列表,推荐关注等模块。在这些列表显示的内容取决于多种因素:Twitter历史,你的关注者和你喜欢的推荐等。因此,我们可以说所有这些数据都是动态的。
尽管这些数据都是动态获取的,但数据显示的方式相同的。这样就可以使用可重用的Web组件。
例如,我们可以将Tweets列表看作是单个的tweet-component
组件列表。我们可以把tweet-componet
看作是一个Shell,它可以接收各种数据,比如用户外、句柄、tweet和用户头像,以及其他一些片段,它们只是以一致的标记显示这些片段。
假设我们希望从服务器上获取一个大数据用于渲染一个组件列表(例如,tweet-component
组件中的列表)。在Vue中,应该首先想到的是v-for
指令。
v-for
指令
v-for
指令用于基于数据源来渲染项目列表。该指令可以在模板元素上使用,并且需要一个特定的语法:
有关于
v-for
更多的介绍可以点击这里进行了解。
我们来看看这个例子。首先,我们假设我们已经获得一个tweets
数据的集合:
const tweets = [
{
id: 1,
name: 'James',
handle: '@jokerjames',
img: 'https://semantic-ui.com/images/avatar2/large/matthew.png',
tweet: "If you don't succeed, dust yourself off and try again.",
likes: 10,
},
{
id: 2,
name: 'Fatima',
handle: '@fantasticfatima',
img: 'https://semantic-ui.com/images/avatar2/large/molly.png',
tweet: 'Better late than never but never late is better.',
likes: 12,
},
{
id: 3,
name: 'Xin',
handle: '@xeroxin',
img: 'https://semantic-ui.com/images/avatar2/large/elyse.png',
tweet: 'Beauty in the struggle, ugliness in the success.',
likes: 18,
}
]
tweets
是tweet
对象集合,每个tweet
包含特定的推特详细信息 —— 一个唯一的标识符、账户名、推特消息等。现在我们尝试使用v-for
指令基于这些数据来渲染tweet-component
列表。
首先,先创建Vue实例——Vue应用程序的核心。我们把实例挂载到id
为app
的DOM元素上,并将tweets
设置为实例数据对象的一部分。
new Vue({
el: '#app',
data: {
tweets
}
});
现在,我们将创建一个tweet-component
组件,通过v-for
指令来渲染tweet
列表。使用全局的Vue.component
构造函数来创建一个名为tweet-component
的组件:
Vue.component('tweet-component', {
template: `
<div class="tweet">
<div class="box">
<article class="media">
<div class="media-left">
<figure class="image is-64x64">
<img :src="tweet.img" alt="Image">
</figure>
</div>
<div class="media-content">
<div class="content">
<p>
<strong>{{tweet.name}}</strong> <small>{{tweet.handle}}</small>
<br>
{{tweet.tweet}}
</p>
</div>
<div class="level-left">
<a class="level-item">
<span class="icon is-small"><i class="fas fa-heart"></i></span>
<span class="likes">{{tweet.likes}}</span>
</a>
</div>
</div>
</article>
</div>
</div>
`,
props: {
tweet: Object
}
});
这里有一些有趣的东西值得我们去关注。
tweet-component
期望props
中的tweet
是一个对象(props:{tweet:Object}
)。如果组件中props
的tweet
不是一个对象,那么Vue将会发出一个警告信息- 使用Mustache语法
{{}}
,将props
的tweet
对象绑定到组件模板 - 组件标记与Bulma的Box元素相似,因为它与
tweet
很类似
在HTML模板中,我们需要把创建的模板挂载到Vue的应用程序中(即,id
为app
的元素中)。在这个标记中,我们将使用v-for
指令来呈现一个tweet
列表。因为tweets
是我们将要迭代的数据集合,所以tweet
将是在指令中使用合适的别名。在每个呈现的tweet-component
组件中,将对组件中props
的tweet
对象做遍历。
<div id="app" class="columns">
<div class="column">
<tweet-component v-for="tweet in tweets" :tweet="tweet"/>
</div>
</div>
不管tweet
对象中有多少条信息,我们的设置将总是以我们所期望的相同标记渲染tweet
中的每条信息。
添加一些CSS样式之后,我们看到的效果将是这样的:
尽管正如我们所预期的一样,程序能正常运行,但在浏览器的控制台中出现一个Vue的提示信息:
[Vue tip]: <tweet-component v-for="tweet in tweets">: component lists rendered with v-for should have explicit keys...
注意,如果你在Codepen环境下运行此代码,你可能无法看到浏览器控制台报上面的提示信息。
为什么可以按我们预期的运行,但Vue会提示要显式的指定key
的值呢?
Key
在使用v-for
指令渲染列表,为每个迭代的元素指定一个key
属性是很常见的做法。这是因为Vue使用key
属性为每个节点的标识创建唯一的绑定。
解释一下。如果我们的列表中有任何动态的UI更改(比如列表项的顺序被打乱),Vue将选择在每个元素中更改数据,而不是相应的移动DOM元素。这在大多数情况下都不是问题。然而,在某此情况下,v-for
渲染出来的列表项依赖于DOM状态和(或)子组件状态,这可能会导致一些非预期的渲染效果。
让我们来看一个例子。如果tweet-component
组件包含了一个允许用户直接输入消息的字段呢?我们将忽略如何提交信息的响应,并简单地处理新输入字段本身:
把这个新的输入字段添加到tweet-component
组件中:
Vue.component('tweet-component', {
template: `
<div class="tweet">
<div class="box">
// ...
</div>
<div class="control has-icons-left has-icons-right">
<input class="input is-small" placeholder="Tweet your reply..." />
<span class="icon is-small is-left">
<i class="fas fa-envelope"></i>
</span>
</div>
</div>
`,
props: {
tweet: Object
}
});
假设我们想要在我们的应用程序中引入另一个新功能,这个功能包括允许用户随机打乱消息列表。
要做到这一点,我们需要先在HTML模板中添加一个"Shuffle!"按钮。
<div id="app" class="columns">
<div class="column">
<button class="is-primary button" @click="shuffle">Shuffle!</button>
<tweet-component v-for="tweet in tweets" :tweet="tweet"/>
</div>
</div>
我们在按钮元素上添加一个单击事件监听器,在触发时将调用shuffle
方法。在我们的Vue实例中需要创建一个shuffle
方法,负责在实例中随机打乱tweets
集合。我们将使用lodash
的_shuffle
方法来实现这个功能。
new Vue({
el: '#app',
data: {
tweets
},
methods: {
shuffle() {
this.tweets = _.shuffle(this.tweets)
}
}
});
来试试效果。如果我们点击shuffle
按钮几次,我们会注意到,我们的tweet
列表元素会被随机分类。
但是,如果我们在每个组件的input
中输入一些信息,然后单击shuffle
按钮,我们会发现有一些奇怪的事情发生,而且超出我们所预期的效果:
由于我们没有使用key
属性,Vue并没有为每个tweet
节点创建唯一的绑定。因此,当我们打算重新对tweets
排序时,Vue采用了更有效的,更节省的方法简单地在对每个元素进行数据的更改(或修补)。由于临时的DOM状态(即输入的文本)仍然存在,造成给我们的体验就如上图所示的效果一样,文本出现意外的匹配。
下图向我们展示了对每个元素和仍然存在的DOM状态进行数据的修补:
为了避免这种情况,我们不得不为tweet-component
组件渲染出来的每个列表项绑定一个key
值。我们将使用tweet
的id
作为唯一标识符,更安全地说,tweet
的id
不应该等于另一个tweet
的id
。因为我们使用的是动态值,所以我们将使用v-bind
指令将我们的key
绑定到tweet.id
:
<div id="app" class="columns">
<div class="column">
<button class="is-primary button" @click="shuffle">Shuffle!</button>
<tweet-component v-for="tweet in tweets" :tweet="tweet" :key="tweet.id" />
</div>
</div>
现在,Vue识别每个tweet
的节点的标识。因此,当我们打算对列表进行调整时,将重新对组件进行排序。
由于tweet-component
组件中的每个列表项都被相应的移动,因此我们可以使用Vue的transition-group
功能把元素重新排序的效果做得更好一些。
为此,我们将把transition-group
元素添加到v-for
列表中。我们将指定tweets
的过渡名称,并声明transition-group
应该做为div
元素渲染。
<div id="app" class="columns">
<div class="column">
<button class="is-primary button" @click="shuffle">Shuffle!</button>
<transition-group name="tweets" tag="div">
<tweet-component v-for="tweet in tweets" :tweet="tweet" :key="tweet.id" />
</transition-group>
</div>
</div>
基于过渡的名称,Vue将自动识别是否已指定了CSS的transition
或animation
。因为我们的目标是调用列表中项目移动时,Vue将会指定CSS的过渡名称tweets-move
(给transition-group
的名称tweets
)。因此,我们将手动引入一个具有指定类型和过渡时间的.tweets-move
的类:
#app .tweets-move {
transition: transform 1s;
}
这只是一个简单的效果。如果要应用更复杂的列表过渡效果,就需要阅读Vue的文档,这样可以了解关于Vue中所有不同类型过渡的详细信息。
当调用shuffle
时,tweet-component
元素将在位置移动时有一个过渡的效果。试一试!在input
中输入一些信息,然后多单击几次"Shuffle!"按钮几次。
很酷,对吧?如果没有key
属性,就不能使用transition-group
元素来创建列表过渡效果,因为元素是修补的而不是被重新排序的。
是否应该始终使用key
属性呢?并非如此,Vue文档推荐只有满足下面的情况之下才使用key
属性:
- 出于性能考虑,我们有意地希望使用默认的修补元素的方式
- DOM内容非常简单
总结
希望文章描述了v-for
指令是如何有用的,并提供了更多的上下文来解释为什么在v-for
指令中要使用key
属性。如果你有任何想法,或者上文中有任何不对之处,欢迎在下面的评论中指出来。
如需转载,烦请注明出处:https://www.w3cplus.com/vue/list-rendering-and-vues-v-for-directive.html