Skip to content

列表渲染

v-for

html
<li v-for="item in items">{{ item.message }}</li>

<li v-for="(item, index) in items">{{ parentMessage }} - {{ index }} - {{ item.message }}</li>

在 v-for 块中可以完整地访问父作用域内的属性和变量。v-for 也支持使用可选的第二个参数表示当前项的位置索引。

javascript
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])

v-for变量的作用域类似于这段代码

javascript
const parentMessage = 'Parent'
const items = [
  /* ... */
]

items.forEach((item, index) => {
  // 可以访问外层的 `parentMessage`
  // 而 `item` 和 `index` 只在这个作用域可用
  console.log(parentMessage, item.message, index)
})

也可以在定义v-for的变量别名时使用结构 :

html
<li v-for="{ message } in items">{{ message }}</li>

<!-- 有 index 索引时 -->
<li v-for="({ message }, index) in items">{{ message }} {{ index }}</li>

多层嵌套的v-for作用域参考下面代码

javascript
<li v-for="item in items">
  <span v-for="childItem in item.children">
    {{ item.message }} {{ childItem }}
  </span>
</li>

也可以使用of来作为分隔符替代 in,更接近 JavaScript 的迭代器语法

html
<div v-for="item of items">{{ item }}</div>

v-for与对象

可以使用v-for来遍历对象.遍历的数序基于对该对象调用Object.value的返回值来决定

html
const myObject = reactive({ title: 'How to do lists in Vue', author: 'Jane Doe', publishedAt:
'2016-04-10' })
html
<ul>
  <li v-for="value in myObject">{{ value }}</li>
</ul>

可以通过提供第二个参表示属性名(例如:key)

html
<ul>
  <li v-for="(value, key) in myObject">{{ key }}: {{ value }}</li>
</ul>

第三个参数表示索引位置

html
<ul>
  <li v-for="(value, key, index) in myObject">{{ index }}. {{ key }}: {{ value }}</li>
</ul>

v-for里使用范围值

html
<div v-for="n in 10">{{ n }}</div>

需要注意,这里n的值是从1开始的,而不是0

template上的v-for

v-for可以放在template上,这样可以避免渲染多余的元素

html
<ul>
  <template v-for="item in items">
    <li>{{ item.text }}</li>
  </template>
</ul>

v-forv-if

当用在同一个节点上时,v-ifv-for优先级高,这意味着v-if将不包含在v-for的循环中,且v-if的条件无法访问到v-for作用域中的变量

html
<!--
 这会抛出一个错误,因为属性 todo 此时
 没有在该实例上定义
-->
<li v-for="todo in todos" v-if="!todo.isComplete">{{ todo.name }}</li>

这个设定在Vue2中是不同的,v-for的优先级更高,为了避免混乱,建议使用template来包裹v-if条件

html
<template v-for="todo in todos">
  <li v-if="!todo.isComplete">{{ todo.name }}</li>
</template>

v-forkey

Vue默认使用"就地更新"的策略来处理列表渲染 :

1.不移动DOM元素

  • 当数组顺序变化时,,不会根据数据的变化来移动DOM元素
  • 例如原数组为 [1, 2, 3],如果变为 [2, 1, 3],则不会移动 DOM 元素,而是就地更新元素内容

2.就地更新DOM元素内容

  • Vue会对比数据变化,但是只会更新每个位置上元素的内容
  • 在上例中,原本显示A的位置现在会更新显示B的内容,原本B的位置显示A的内容

3.保留DOM状态

  • 保留DOM状态,如input输入框中的输入内容,textarea中的文本内容,select中的选中状态等

默认模式是高俏的,但只适用于列表渲染输出的结果不依赖子组件状态或者临时DOM状态(例如表单输入值)的情况只有在纯数据展示的情况下才key才可以省略 不进行任何交互,不涉及任何其他数据的状态.但是建议在使用时默认都加上key养成习惯

key的作用是给Vue提供一个唯一的标识符,以便在数据变化时,可以更高效地更新DOM元素,在设置key的时候需要保证key的唯一性

html
<ul>
  <li v-for="item in items" :key="item.id">{{ item.text }}</li>
</ul>

key的绑定至期望是一个基础类型的值,不要使用对象作为`key

组件上使用v-for

html
<MyComponent v-for="item in items" :key="item.id" />

如果需要将迭代的数据传递给组件 :

html
<MyComponent v-for="item in items" :item="item" :key="item.id" />

数组变化侦测

变更方法

Vue会自动侦测数组的变更方法,并在他们被调用的时候出发相关的更新,这些方法包括 :

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

之外的方法不会被Vue侦测到,例如直接通过索引来修改数组

变异方法

这些方法会变更调用的原数组

javascript
items.push({ message: 'Baz' })

非变异方法

这些方法不会变更调用的原数组,而是返回一个新的数组

  • filter() - 过滤数组
  • concat() - 合并数组
  • slice() - 截取数组
  • map() - 映射新数组
  • flat()/flatMap() - 扁平化数组

使用这些方法时需要将旧数组替换为返回的新数组来触发更新

展示过滤或排序过后的结果

javascript
const numbers = ref([1, 2, 3, 4, 5])

const evenNumbers = computed(() => {
  return numbers.value.filter((n) => n % 2 === 0)
})
html
<li v-for="n in evenNumbers">{{ n }}</li>

这里筛选出了偶数,并将其作为v-for的迭代对象

在多层嵌套时,简单使用计算属性就不可行了,可以使用这种方法 :

javascript
const sets = ref([
  [1, 2, 3, 4, 5],
  [6, 7, 8, 9, 10]
])

function even(numbers) {
  return numbers.filter((number) => number % 2 === 0)
}
html
<ul v-for="numbers in sets">
    <li v-for="n in even(numbers)">{{ n }}</li>
</ul>

TIP 之前提到过有些方法会改变数组的元数据,在计算属性中使用这些方法的时候务必小心,要保证计算属性中不应该有其他影响的原则 :

javascript
// 错误用法
return numbers.reverse()
// 正确用法
return [...numbers].reverse()