前端开发者学堂 - fedev.cn

列表渲染和Vue的v-for指令

发布于 大漠

特别声明:此篇文章内容来源于@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,
    }
]

tweetstweet对象集合,每个tweet包含特定的推特详细信息 —— 一个唯一的标识符、账户名、推特消息等。现在我们尝试使用v-for指令基于这些数据来渲染tweet-component列表。

首先,先创建Vue实例——Vue应用程序的核心。我们把实例挂载到idapp的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})。如果组件中propstweet不是一个对象,那么Vue将会发出一个警告信息
  • 使用Mustache语法{{}},将propstweet对象绑定到组件模板
  • 组件标记与Bulma的Box元素相似,因为它与tweet很类似

在HTML模板中,我们需要把创建的模板挂载到Vue的应用程序中(即,idapp的元素中)。在这个标记中,我们将使用v-for指令来呈现一个tweet列表。因为tweets是我们将要迭代的数据集合,所以tweet将是在指令中使用合适的别名。在每个呈现的tweet-component组件中,将对组件中propstweet对象做遍历。

<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值。我们将使用tweetid作为唯一标识符,更安全地说,tweetid不应该等于另一个tweetid。因为我们使用的是动态值,所以我们将使用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的transitionanimation。因为我们的目标是调用列表中项目移动时,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属性。如果你有任何想法,或者上文中有任何不对之处,欢迎在下面的评论中指出来。

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.fedev.cn/vue/list-rendering-and-vues-v-for-directive.htmlAir Jordan VI 6 Shoes