Vue3手册译稿 - 基础 - 列表渲染

使用v-for将数组映射成元素列表

可以使用v-for指令将数组渲染成一个列表。v-for指令在使用特殊语法item in items,items指向数组源,item是迭代 items的每项的别名:

<ul id="array-rendering">
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>
Vue.createApp({
  data() {
    return {
      items: [{ message: 'Foo' }, { message: 'Bar' }]
    }
  }
}).mount('#array-rendering')

v-for块内我们有权限访问父级属性。同时v-for提供了第二个参数index作为循环的索引:

<ul id="array-with-index">
  <li v-for="(item, index) in items">
    {{ parentMessage }} - {{ index }} - {{ item.message }}
  </li>
</ul>
Vue.createApp({
  data() {
    return {
      parentMessage: 'Parent',
      items: [{ message: 'Foo' }, { message: 'Bar' }]
    }
  }
}).mount('#array-with-index')

你可以使用of分隔符来代替in,跟javascript的迭代语法更加接近:

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

v-for遍历对象

你也可以使用v-for遍历对象的每个元素。

<ul id="v-for-object" class="demo">
  <li v-for="value in myObject">
    {{ value }}
  </li>
</ul>
Vue.createApp({
  data() {
    return {
      myObject: {
        title: 'How to do lists in Vue',
        author: 'Jane Doe',
        publishedAt: '2016-04-10'
      }
    }
  }
}).mount('#v-for-object')

同时你可以使用第二个 name参数(a.k.a key)

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

还可以使用第三个参数索引:

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

[info] 提示
迭代一个对象时,根据object.keys()的列表进行排序,不能保证与javascript引擎实现保持一致!

维护状态

v-for渲染元素的列表被Vue更新时,默认使用“就地更新”策略。如果数据项的顺序更新了,不使用移动DOM元素来更新顺序,Vue会就地更新元素,并保证每个元素映射到被渲染的位置。
这是一种高效的模式,但这只适用于你的渲染列表不依赖于子组件状态和临时DOM状态(例如表单输入的值)。
Vue会给每一行一个唯一键,从而重用和重新排序每一个现有元素,你需要提供一个唯一的key属性:

<div v-for="item in items" :key="item.id">
  <!-- content -->
</div>

推荐不论是否使用,都为v-for提供一个唯一的key,除非迭代非常之简单,或者故障依靠默认行为来提升性能:
因为它是Vue标识唯一节点的通用机制,key不只仅针对v-for,还有其它的用途,手册后面会介绍。

[info] key不要使用非基本类型如对象或数组。应使用字符或数字。

key使用方法细节,详见Key 接口文档

这段太难理解了,从字面意思上理解,当数组排序发生变化时,Vue不会傻傻的一个个搬DOM标签的位置,而是就地更新,比如长度为10的数组,删除第8个,只会把第9,10更新成8,9。然后特别指明如表单输入等临时状态不适用,除非为每一行指定一个临时的key。
下面这段代码,如果不为每行指定一个:key="item.index",则删除效果是错误的,因为你输入文本框的内容是“临时的”。

<html lang="en-US">
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width,initial-scale=1">
      <title>vue demo1</title>
      <script src="https://unpkg.com/vue@next"></script>
     </head>
     <body>
        <div id="app">
            <ul>
                <li v-for="(item,index) in users" :key="item.index">
                    {{index}}-{{item.index}}
                    <input type="text" />
                    <button @click="del(index)">删除</button>
                </li>
            </ul>
        </div>
        <script type="text/javascript">
            Vue.createApp({
                data() {
                    return {
                        users: [
                            {index:1,value:''},
                            {index:2,value:''},
                            {index:3,value:''},
                            {index:4,value:''},
                            {index:5,value:''},
                        ]
                    }
                },
                methods: {
                    del(index) {
                        if(confirm('确实要删除此行吗?')){
                            this.users.splice(index,1)
                        }
                    }
                }
            }).mount('#app')
        </script>
    </body>
</html>

数组变化监控

变更数组的方法

Vue含有一个数组变化的监视器,同时它也会监控视图变化。方法如下:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
    原生js方法,自己测试吧,不啰嗦了。

数组替换

例如:filter(),concat,slice(),不会理性原数组,会返回一个新的数组。可以这样把原数组给替换掉:

example1.items = example1.items.filter(item => item.message.match(/Foo/))

你可能会认为这会导致 Vue抛弃原有的DOM并重新渲染整个列表 - 很幸运,Vue智能的最大化重用DOM元素,所以用一个数组替换有部分重叠的另一个数组是非常高效的。

显示过滤/分类的结果

有时候我们想显示过滤或分类后的数组,而不改变或重置原数组。可以使用计算属性来返回这样的结果:

<li v-for="n in evenNumbers" :key="n">{{ n }}</li>
data() {
  return {
    numbers: [ 1, 2, 3, 4, 5 ]
  }
},
computed: {
  evenNumbers() {
    return this.numbers.filter(number => number % 2 === 0)
  }
}

在计算属性不行不通的方案下(例如v-for循环嵌套),你还可以使用方法:

<ul v-for="numbers in sets">
  <li v-for="n in even(numbers)" :key="n">{{ n }}</li>
</ul>
data() {
  return {
    sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
  }
},
methods: {
  even(numbers) {
    return numbers.filter(number => number % 2 === 0)
  }
}

v-for遍历一个范围

v-for可以遍历一个整数,下面的示例中会重复模板10次:

<div id="range" class="demo">
  <span v-for="n in 10" :key="n">{{ n }} </span>
</div>

v-for使用在一个<template>标签上

v-if一样,你可以在<template标签上使用v-for来渲染多个元素。例如(template标签并不会渲染到html):

<ul>
  <template v-for="item in items" :key="item.msg">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

v-forv-if一同使用

[info] 不推荐两个一起使用。参考风格手册获取细节。

如果它们出现在同一节点中,v-if优先级高于v-for。这意味着v-if不能使用v-for中的变量。

<!-- 这会抛出错误,因为todo变量未定义. -->
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>

可以把v-if包裹到<template>标签内部来解决这个问题:

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

组件中使用v-for

[info]这个章节认为你已掌握组件相关知识。如果不了解请先跳过。

你可以像普通元素一样直接在组件上使用v-for

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

但这种方式不会传递任何参数到组件,因为组件权局限内部范围。为了把迭代数据传到组件内,我们使用props

<my-component
  v-for="(item, index) in items"
  :item="item"
  :index="index"
  :key="item.id"
></my-component>

item自动注入到组件内部耦合性太强,显式的传递参数可以让组件在其他场景下可复用度更高。看一个待办列表管理的例子(实现待办列表的增加和删除):

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>v-for-template</title>
	<script src="https://unpkg.com/vue@next"></script>
   </head>
   <body>
		<div id="app">
			<form v-on:submit.prevent="addNewTodo">
				<label for="new-todo">新增待办:</label>
				<input id="new-todo" placeholder="例如:喂猫" v-model="newToDoText" />
				<button>添加</button>
			</form>
			<ul>
				<todo-item
					v-for="(item,index) in todos"
					:key="item.id"
					:title="item.title"
					:index="index"
					@remove="remove($event)"
				></todo-item>
			</ul>
		</div>
   </body>
   <script type="text/javascript">
	const data = {
		data() {
			return {
				todos: [
					{id:1,title:"洗碗"},{id:2,title:"扔垃圾"},{id:3,title:"修剪草坪"}
				],
				newToDoText: '',
				newToDoId: 4
			}
		},
		methods: {
			addNewTodo() {
				if(this.newToDoText == ''){
					alert('空空空!');
				}else {
					this.todos.push({
						id: this.newToDoId++,
						title: this.newToDoText
					})
					this.newToDoText = ''
				}
			},
			remove(index) {
				this.todos.splice(index,1)
			}
		}
	}
	const app = Vue.createApp(data)
	app.component('todo-item',{
		template: `
			<li>
				{{ title }}
				<button @click="removeItem(index)">删除</button>
			</li>
		`,
		props: ['title','index'],
		emits: ['remove'],
		methods: {
			removeItem(item_index){
				this.$emit('remove',item_index)
			}
		}
	})
	app.mount("#app")
   </script>
</html>

posted on 2021-03-04 12:33  zhouyu  阅读(144)  评论(0编辑  收藏  举报

导航