首页 > 编程笔记 > JavaScript笔记 阅读:8

Vue v-for列表渲染指令的用法(非常详细)

在项目开发的页面中,有很多列表效果需要动态显示,这在 Vue 中被称为列表渲染,可以通过 v-for 指令来实现。

通过 v-for 指令,可以遍历多种不同类型的数据,数组是较为常见的一种类型,当然类型还可以是对象或数值。

动态显示列表后,我们可以对列表进行添加、删除、修改、过滤、排序等操作。在通过 v-for 指令进行列表渲染时,有一个特别重要的属性 key,本节会对其背后的原理专门进行分析。

Vue列表的动态渲染

当我们在模板中通过 v-for 指令来遍历数组显示列表时,指令的完整写法如下:
v-for="(item, index) in array"
其中,item 为被遍历的 array 数组的元素别名,index 为数组元素的下标。item 和 index 的名称不是固定的,也可以使用其他的合法名称。

如果不需要下标,则可以简化为“v-for="item in array"”。

当我们使用 v-for 指令遍历一个对象时,遍历的是对象自身所有可遍历的属性,指令的完整写法如下:
v-for="(value, name) in obj"
其中,value 为被遍历的 obj 对象的属性值,name 为属性名。value 和 name 的名称不是固定的,也可以使用其他的合法名称。

如果只需要属性值,则可以简化为“v-for="value in object"”。

当 v-for 指令遍历的目标是一个正整数 n 时,其一般用于让当前模板在 1~n 的取值范围内重复产生n次,指令的完整写法如下:
v-for="value in n"
其中,value 为从 1 开始到 n 为止依次递增的数值。

请思考下方代码的运行效果,Vue 代码如下:
<div id="app">
  <h2>测试遍历数组<人员列表>(用得很多)</h2>
  <ul>
    <li v-for="(p,index) in persons">
      {{index}}--{{p.name}}--{{p.age}}
    </li>
  </ul>

  <h2>测试遍历对象<汽车信息>(用得较少)</h2>
  <ul>
    <li v-for="(value,name) in car">
      {{name}}--{{value}}
    </li>
  </ul>

  <h2>测试遍历指定次数(用得较少)</h2>
  <ul>
    <li v-for="value in num">
      {{value}}
    </li>
  </ul>
</div>

JavaScript 代码如下:
const { createApp } = Vue

createApp({
  data () {
    return {
      persons: [
        {id:'001',name:'张三',age:22},
        {id:'002',name:'李四',age:24},
        {id:'003',name:'王五',age:23}
      ],
      car: {
        name:'奥迪A8',
        price:'70万元',
        color:'黑色'
      },
      num: 5
    }
  }
}).mount('#app')
在上方代码中,分别演示了遍历的数据类型为数组、对象、数值的 3 种情况。这部分代码比较简单,我们直接来看页面效果,如下图所示:


图 1 页面效果(1)

与 v-if 指令类似,我们也可以在 template 标签上使用 v-for 指令,实现对多个标签的列表渲染。具体代码如下:
<h2>在template标签上使用v-for指令</h2>
<ul>
  <template v-for="p in persons">
    <li>{{p.name}}--{{p.age}}</li>
    <span>来自C语言中文网学员</span>
  </template>
</ul>
上方代码在 template 标签上使用 v-for 指令实现了标签列表的循环,此时页面中的每个 li 和 span 都是一组:


图 2 页面效果(2)

此时我们可以利用 v-for 指令对数据进行循环,但是在阅读 Vue 官方文档时可以发现,Vue 官方建议在使用 v-for 指令进行列表渲染时,最好同时指定唯一的 key 属性。

这个 key 属性可以提高列表更新的性能,且 key 属性的值最好不要使用下标,因为某个数组元素的下标是不稳定的,这样会影响内部 DOM Diff 算法的效率,甚至有可能产生错误的效果。

key 属性的值应该是数组元素对应的稳定的数值或字符串值,比如数组元素的 id 属性。现在对前面编写的人员列表代码进行完善,完整代码应该是下方这样的:
<h2>在使用v-for指令列表渲染时指定唯一的key属性</h2>
<ul>
  <li v-for="(p,index) in persons" :key="p.id">
    {{index}}--{{p.name}}--{{p.age}}
  </li>
</ul>

Vue列表的增、删、改

在实际项目开发中,在动态显示数组列表后,一般不会使其只作为展示存在,可能还需要对其进行增、删、改操作。

下图展示了一个简易的列表,初始显示张三、李四、王五共 3 个人员信息列表,请读者思考如何实现页面效果和需求。


图 3 简易的列表

用户需求如下:
实现代码如下所示,Vue 代码如下:
<div id="app">
  <h2>测试列表的增、删、改</h2>
  <ul>
    <li v-for="(p,index) in persons" :key="p.id" style="margin-top: 5px;">
      {{index}}--{{p.name}}--{{p.age}}
      <!-- <button @click="deleteItem(index)">删除</button>
      <button @click="updateItem(index, {id: Date.now(), name: '小兰', age: 12})">更新</button> -->
    </li>
  </ul>
  <button @click="addFirst({id: Date.now(), name: '小明', age: 10})">向第一位添加</button>
  --
  <button @click="addLast({id: Date.now(), name: '小红', age: 11})">向最后一位添加</button>
</div>
JavaScript 代码如下:
const { createApp } = Vue

createApp({
  data () {
    return {
      persons: [
        {id:'001',name:'张三',age:18},
        {id:'002',name:'李四',age:19},
        {id:'003',name:'王五',age:20}
      ]
    }
  },
  methods: {
    addFirst (newP) { // 在第一个人员信息列表前面添加
      this.persons.unshift(newP)
    },
    addLast (newP) { // 在最后一个人员信息列表后面添加
      this.persons.push(newP)
    },
    deleteItem (index) { // 删除指定下标的人员信息
      this.persons.splice(index, 1)
    },
    updateItem (index, newP) { // 将指定下标的人员信息替换为新添加的人员信息
      this.persons.splice(index, 1, newP)
    }
  }
}).mount('#app')
在上方代码中为每个功能按钮都封装了相应的方法来实现对应的功能,从而实现对数组列表的增、删、改操作。其实每个功能方法内部,都是通过调用数组变更内部元素的方法(包括 unshift、push 和 splice 方法)来实现的。

那读者可能会想,如果不调用这些方法,直接通过下标来添加或者替换数组列表,能触发页面自动更新吗?答案是:在 Vue2 中不可以,但在 Vue3 中是完全可以。而通过调用数组变更内部元素的方法来操作元素,不管是 Vue2 还是 Vue3,都是可以触发页面自动更新的。这是因为 Vue 内部对 data 中的数组的一系列变更方法进行了重写包装,被包装的变更方法一共有 7 个,分别是 push、pop、shift、unshift、splice、sort 和 reverse 方法,这 7 个方法内部会先对数组列表进行相应操作,再更新页面。

在 Vue3 中,如果想在最后一个人员信息列表后面添加一条人员信息,就可以使用下方代码:
this.persons[this.persons.length] = newP
如果将指定下标的人员信息替换为新添加的人员信息,就可以使用下方代码:
this.persons[index] = newP

Vue列表的过滤

对列表进行过滤显示,是项目开发中常见的功能之一。

列表过滤分为两种,分别是后台过滤和前台过滤:
本节要实现的就是前台的列表过滤效果。现在的需求为:当输入框没有任何输入时,显示所有人员的列表;当输入姓名时,下方会实时显示所有匹配的人员列表。

初始页面效果如下图所示:


图 4 初始页面效果

过滤页面效果如下图所示:


图 5 过滤页面效果

我们先对上方需求进行分析,然后编写实现代码。

页面由标题、输入框和列表组成。对于输入框输入的姓名,可以使用 v-model 指令将输入的数据收集到 data 对象的 keyword 属性中。

下方显示的列表无疑是通过遍历数据得到的,但是这个数据不能直接是所有人员的列表(初始列表),它需要是一个根据 keyword 属性进行过滤后的列表。对于这部分的处理,我们可以现在 data 对象中定义包含所有人员的数组 persons,再定义一个根据 keyword 属性进行过滤的计算属性 filterPersons,其过滤的条件就是人员的 name 中包含 keyword 属性。

此时整体逻辑已经十分清晰,读者可以先尝试写出实现代码,再对照下方实现代码进行优化。如果思路还是不清晰,那么可以先对下方代码进行阅读思考,再独立写出实现代码。

Vue 代码如下:
<div id="app">
  <h2>人员列表</h2>
  <input type="text" placeholder="请输入姓名" v-model="keyword">
  <ul>
    <li v-for="(p,index) of filterPersons" :key="index">
      {{p.name}}-{{p.age}}-{{p.sex}}
    </li>
  </ul>
</div>

JavaScript 代码如下:
const { createApp } = Vue

createApp({
  data () {
    return {
      persons: [ // 所有人员的列表
        {id:'001', name: '廖景山', age: 21, sex: '男'},
        {id:'002', name: '熊小巧', age: 19, sex: '女'},
        {id:'003', name: '吴天山', age: 20, sex: '男'},
        {id:'004', name: '谢思萌', age: 22, sex: '女'}
      ],
      keyword: '', // 输入的姓名
      sortType: 0, // 0代表原顺序,1代表降序,2代表升序
    }
  },
  computed: {
    // 过滤并排序后的人员列表
    filterPersons () { // 过滤后用于显示人员列表的计算属性
      return this.persons.filter((p) => {
        return p.name.indexOf(this.keyword) !== -1
      })
    }
  }
}).mount('#app')

Vue列表的排序

前面对列表的过滤进行了相关讲解,对于大部分网站和用户来讲,这个功能并不够完善。下面就在前面案例的基础上对需求进行升级。

现在的需求为:在对人员列表进行过滤的同时,提供排序的功能。需要增加 3 个按钮,分别是“年龄升序”、“年龄降序” 和 “原顺序”,点击“年龄升序”或“年龄降序”按钮,根据年龄对人员列表进行升序或者降序处理,点击“原顺序”按钮,人员列表变为原本顺序。下面就来实现新增的需求。

根据需求,我们需要实现如下图所示的页面效果:


图 6 页面效果

下面对这个需求进行分析。过滤后的人员列表有 3 种状态:升序、降序和原顺序。这里可以设计一个 data 数据 sortType 来标识这 3 种状态。因为有 3 种状态,所以不能将其设计为布尔类型,number 类型和字符串类型都可以满足,这里将其设计为 number 类型。用 0 代表原顺序,1 代表降序,2 代表升序,值指定为 0。

点击按钮时,sortType 更新相应的数值。在 filterPersons 计算属性中,先根据 keyword 产生一个过滤后的数组,再根据 sortType 选择是否进行排序,从而决定是进行升序排列还是降序排列。

此时整体逻辑已经十分清晰,读者可以先尝试写出实现代码,再对照下方实现代码进行优化。如果思路还是不清晰,则可以先对下方代码进行阅读思考,再独立写出实现代码。

Vue 代码如下:
<div id="app">
  <h2>人员列表</h2>
  <input type="text" placeholder="请输入姓名" v-model="keyword">
  <ul>
    <li v-for="(p,index) of filterPersons" :key="p.id">
      {{p.name}}-{{p.age}}-{{p.sex}}
    </li>
  </ul>
  <button @click="sortType = 2">年龄升序</button> --
  <button @click="sortType = 1">年龄降序</button> --
  <button @click="sortType = 0">原顺序</button>
</div>

JavaScript代码如下:
const { createApp } = Vue

createApp({
  data () {
    return {
      persons: [ // 所有人员的列表
        {id:'001', name: '廖景山', age: 21, sex: '男'},
        {id:'002', name: '熊小巧', age: 19, sex: '女'},
        {id:'003', name: '吴天山', age: 20, sex: '男'},
        {id:'004', name: '谢思萌', age: 22, sex: '女'}
      ],
      keyword: '', // 输入的姓名
      sortType: 0, // 0代表原顺序,1代表降序,2代表升序
    }
  },
  computed: {
    // 过滤并排序后的人员列表
    filterPersons () {
      // 取出依赖数据
      const {persons, keyword, sortType} = this
      // 对总数组进行过滤产生过滤后的数组
      const arr = persons.filter((p) => {
        return p.name.indexOf(keyword) !== -1
      })
      // 只有当sortType的值不为0时,才进行排序
      if (sortType !== 0) {
        arr.sort((p1, p2) => {
          if (sortType === 1) { // 降序
            return p2.age - p1.age
          } else { // 升序
            return p1.age - p2.age
          }
        })
      }
      return arr
    }
  }
}).mount('#app')

相关文章