Vue的条件渲染及列表渲染

条件渲染

v-if

1、在 Vue.js ,我们使用 v-if 指令实现同样的功能:

1
2
3
4
5
<h1 v-if="ok">Yes</h1>
//也可以用 v-else 添加一个 “else” 块:当 v-if 的值为假时,v-else 可见。
//v-else 元素必须紧跟在 v-if 或者 v-else-if 元素的后面——否则它将不会被识别。
<h1 v-if="ok">Yes</h1>
<h1 v-else>No</h1>

2、在<template>中配合 v-if 条件渲染一整组

因为 v-if 是一个指令,需要将它添加到一个元素上。但是如果我们想切换多个元素呢?此时我们可以把一个<template>元素当做包装元素,并在上面使用 v-if。最终的渲染结果不会包含<template>元素。下图可以很清晰地看出:


3、v-else-if(2.1.0 新增)
v-else-if,顾名思义,充当 v-if 的“else-if 块”。可以链式地使用多次:

1
2
3
4
5
6
7
8
9
10
11
12
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>

类似于 v-else,v-else-if 必须紧跟在 v-if 或者 v-else-if 元素之后。

4、用key管理可复用的元素
高效地复用元素除了可以提高渲染速度,还可以让代码简洁:

1
2
3
4
5
6
7
8
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>

那么在上面的代码中切换 loginType 将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,<input>不会被替换掉——仅仅是替换了它的 placeholder。
若需要一种方式来声明“这两个元素是完全独立的——不要复用它们”。只需添加一个具有唯一值的 key 属性即可:

1
2
3
4
5
6
7
8
9
10
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
//现在,每次切换时,输入框都将被重新渲染。
//注意,<label> 元素仍然会被高效地复用,因为它们没有添加 key 属性。

v-show

另一个用于根据条件展示元素的选项是 v-show 指令。用法大致一样:

1
<h1 v-show="ok">Hello!</h1>

不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 是简单地切换元素的 CSS 属性 display 。
注意,v-show 不支持<template>语法,也不支持 v-else。

v-if vs v-show

v-if 是“真正的”条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件不太可能改变,则使用 v-if 较好。

v-ifv-for 一起使用

当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级。

列表渲染

v-for把一个数组对应为一组元素

我们用 v-for 指令根据一组数组的选项列表进行渲染。v-for 指令需要使用 item in items 形式的特殊语法,items 是源数据数组并且 item 是数组元素迭代的别名。

1
2
3
4
5
<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>

1
2
3
4
5
6
7
8
9
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})

结果:

  • Foo

  • Bar

  • 在 v-for 块中,我们拥有对父作用域属性的完全访问权限。v-for 还支持一个可选的第二个参数为当前项的索引。
    1
    2
    3
    4
    5
    <ul id="example-2">
    <li v-for="(item, index) in items">
    {{ parentMessage }} - {{ index }} - {{ item.message }}
    </li>
    </ul>


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var example2 = new Vue({
    el: '#example-2',
    data: {
    parentMessage: 'Parent',
    items: [
    { message: 'Foo' },
    { message: 'Bar' }
    ]
    }
    })


    结果:

  • Parent - 0 - Foo

  • Parent - 1 - Bar

  • 你也可以用 of 替代 in 作为分隔符,因为它是最接近 JavaScript 迭代器的语法:
    1
    <div v-for="item of items"></div>


    ### 一个对象的v-for
    你也可以用 v-for 通过一个对象的属性来迭代。
    1
    2
    3
    4
    5
    <ul id="v-for-object" class="demo">
    <li v-for="value in object">
    {{ value }}
    </li>
    </ul>


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    new Vue({
    el: '#v-for-object',
    data: {
    object: {
    firstName: 'John',
    lastName: 'Doe',
    age: 30
    }
    }
    })


    结果:

  • John

  • Doe

  • 30
  • 也可以提供第二个参数为键名,第三个参数为索引:

    1
    2
    3
    4
    5
    <ul id="v-for-object" class="demo">
    <li v-for="(value, key, index) in object">
    {{ index }}. {{ key }}: {{ value }}
    </li>
    </ul>

    key

    当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值)的列表渲染输出。为了区别,就要为每项提供一个唯一 key 属性。理想的 key 值是每项都有的且唯一的 id。

    1
    2
    3
    <div v-for="item in items" :key="item.id">
    <!-- 内容 -->
    </div>

    建议尽可能在使用 v-for 时提供 key,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。

    变异方法

    Vue 包含一组观察数组的变异方法,所以它们也将会触发视图更新。这些方法如下:

  • push():往数组最后面添加一个元素,成功返回当前数组的长度。
  • pop():删除数组的最后一个元素,成功返回删除元素的值。
  • shift():删除数组的第一个元素,成功返回删除元素的值。
  • unshift():往数组最前面添加一个元素,成功返回当前数组的长度。
  • splice():有三个参数,第一个是想要删除的元素的下标(必选),第二个是想要删除的个数(必选),第三个是删除后想要在原位置替换的值(可选)。
  • sort():使数组按照字符编码默认从小到大排序,成功返回排序后的数组。
  • reverse():将数组倒序,成功返回倒序后的数组。

  • 例如:对 items 数组调用变异方法:example1.items.push({ message: 'Baz' })

    替换数组

    变异方法会改变被这些方法调用的原始数组。相比之下,也有非变异方法,例如:filter(), concat() 和 slice()。这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组:

  • concat():它可以创建当前数组的一个副本,然后将接受到的参数添加到这个副本的末尾,最后返回新构建的数组。
  • slice():slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。这里又产生了两种情况:(1)在只有一个参数的情况下,该方法返回从该参数指定位置开始到当前数组末尾的所有项。(2)如果有两个参数,该方法返回起始和结束位置之间的项,注意但不包括结束位置的项。

  • 注意:如果slice()方法的参数中有一个负数,则用数组长度加上该数来确定相应的位置。如,在一个包含5项的数组上调用slice(-2,-1)与调用slice(3,4)得到的结果相同。如果结束位置小于起始位置,则返回空数组。

    1
    2
    3
    4
    //filter()方法起过滤作用
    example1.items = example1.items.filter(function (item) {
    return item.message.match(/Foo/) //匹配 item.message 中含有/Foo/的项并返回
    })

    注意事项

    由于 JavaScript 的限制,Vue 不能检测以下变动的数组:
    当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
    当你修改数组的长度时,例如:vm.items.length = newLength
    为了解决第一类问题,以下两种方式都可以实现和vm.items[indexOfItem] = newValue相同的效果,同时也将触发状态更新:

    1
    2
    3
    4
    // Vue.set
    Vue.set(example1.items, indexOfItem, newValue)
    // Array.prototype.splice
    example1.items.splice(indexOfItem, 1, newValue)

    为了解决第二类问题,你可以使用 splice:

    1
    example1.items.splice(newLength)

    对象更改检测注意事项

    还是由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除:

    1
    2
    3
    4
    5
    6
    7
    8
    var vm = new Vue({
    data: {
    a: 1
    }
    })
    // `vm.a` 现在是响应式的
    vm.b = 2
    // `vm.b` 不是响应式的

    对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。但是,可以使用Vue.set(object, key, value)方法向嵌套对象添加响应式属性。例如,对于:

    1
    2
    3
    4
    5
    6
    7
    var vm = new Vue({
    data: {
    userProfile: {
    name: 'Anika'
    }
    }
    })

    你可以添加一个新的 age 属性到嵌套的 userProfile 对象:Vue.set(vm.userProfile, 'age', 27)

    你还可以使用vm.$set实例方法,它只是全局 Vue.set 的别名:this.$set(this.userProfile, 'age', 27)

    有时你可能需要为已有对象赋予多个新属性,比如使用 Object.assign() 或 _.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,不要像这样:

    1
    2
    3
    4
    Object.assign(this.userProfile, {
    age: 27,
    favoriteColor: 'Vue Green'
    })

    你应该这样做:

    1
    2
    3
    4
    this.userProfile = Object.assign({}, this.userProfile, {
    age: 27,
    favoriteColor: 'Vue Green'
    })

    显示过滤/排序结果

    有时,我们想要显示一个数组的过滤或排序副本,而不实际改变或重置原始数据。在这种情况下,可以创建返回过滤或排序数组的计算属性。
    在计算属性不适用的情况下 (例如,在嵌套 v-for 循环中) 你可以使用一个 method 方法。

    v-for 的其他用法

    v-for 也可以取整数。在这种情况下,它将重复多次模板。例如:v-for="n in 10",结果为:12345678910。
    类似于 v-if,你也可以利用带有 v-for 的<template>渲染多个元素。

    当处于同一节点时,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。当你想为仅有的一些项渲染节点时,这种优先级的机制会十分有用,如下:

    1
    2
    3
    <li v-for="todo in todos" v-if="!todo.isComplete">
    {{ todo }}
    </li>

    上面的代码只传递了未 complete 的 todos。
    而如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素 (或 <template>)上。如:

    1
    2
    3
    4
    5
    6
    <ul v-if="todos.length">
    <li v-for="todo in todos">
    {{ todo }}
    </li>
    </ul>
    <p v-else>No todos left!</p>

    组件中的 v-for:在自定义组件里,你可以像任何普通元素一样用 v-for 。

    1
    <my-component v-for="item in items" :key="item.id"></my-component>

    注意:2.2.0+ 的版本里,当在组件中使用 v-for 时,key 现在是必须的。

    组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。父组件的数据需要通过 prop 才能下发到子组件中。
    子组件要显式地用 props 选项声明它预期的数据:

    1
    2
    3
    4
    5
    6
    7
    Vue.component('child', {
    // 声明 props
    props: ['message'],
    // 就像 data 一样,prop 也可以在模板中使用
    // 同样也可以在 vm 实例中通过 this.message 来使用
    template: '<span>{{ message }}</span>'
    })

    然后我们可以这样向它传入一个普通字符串:

    1
    <child message="hello!"></child>

    结果显示:hello!

    HTML 特性是不区分大小写的。所以,当使用的不是字符串模板时,camelCase (驼峰式命名) 的 prop 需要转换为相对应的 kebab-case (短横线分隔式命名):

    1
    2
    3
    4
    5
    Vue.component('child', {
    // 在 JavaScript 中使用 camelCase
    props: ['myMessage'],
    template: '<span>{{ myMessage }}</span>'
    })

    1
    2
    <!-- 在 HTML 中使用 kebab-case -->
    <child my-message="hello!"></child>

    如果你使用字符串模板,则没有这些限制。

    (……props 的详细用法在组件一章中会阐述)