(day6-2)
一、用 v-for 把一个数组对应为一组元素
我们可以使用 v-for 指令基于一个数组来渲染一个列表。
v-for 指令需要 “item in items” 的特殊语法,其中items指源数据数组,item指被迭代的数组元素的别名。
简单例子前面第一天已经有描述,这里就不赘述了。
#在 v-for 中的第二个参数
- v-for 还支持可选的第二个参数,表示当前项的索引;
- v-for 块中还可以访问父作用域的property。
<ul id="example1">
<li v-for="(item,index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
var example1 = new Vue({
el:"example1",
data:{
parentMessage:'parent',
items:{
{message:'Foo'},
{message:'Bar'}
}
}
});
那么对于上面HTML中的v-for,将得到的结果为:
parent-0-Foo
parent-1-Bar
# 也可以使用 of 替代 in 作为分隔符,因为它更接近JavaScript的迭代器语法。
<div v-for="item of items"></div>
二、在 v-for 中使用对象
- 可以使用v-for 来遍历一个对象的property。
- 可以传入第二个参数 为 property的名称(即键名)。
- 可以传入第三个参数作为索引。
<div v-for="(value,name,index) in object">
{{ index }}. {{ name }}: {{ value }}
</div>
data:{
object:{
title:'How to do lists in Vue',
author:'Jane Doe',
publishedAt:'2016-04-10'
}
}
将会得到如下结果:
0.title:How to do lists in Vue
1.author:Jane Doe
2.publishedAt:2016-04-10
注意:在遍历对象时,会按 Object.keys() 的结果遍历,但不能保证它的结果在不同的JavaScript引擎下都一致。
区分一下遍历对象属性时使用的 for-in 、Object.keys()、Object.getOwnpropertyNames()。
以及查找属性的方法——property in Obeject 、hasOwnproperty()。这些都是在ES5原型语法中讲到的遍历属性的方法。
其中 for-in 、Object.keys()、Object.getOwnpropertyNames()三个方法使用来遍历属性,property in Obeject 、hasOwnproperty()使用查找属性是否存在于对象上或者对象上。
可以用这个图来简单的表示:(为了便于表示,上面我将实例和原型中不可枚举属性和可枚举属性分开了)
- for-in 中的in单独使用时(即上面的 property in object),in操作符会在通过对象能够访问给定属性时返回true,无论属性在实例中还是原型中。
- object.hasOwnProperty(),只有属性位于实例中时,才会返回true。
- Object.keys(),只有属性在实例中,且为可枚举时,返回一个包含所有可枚举属性的字符串数组;
- for-in,返回所有能够通过对象访问的、可枚举的属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。
- Object.getOwnPropertyNames(),所以的实例属性,无论是是否可枚举。
这里的实例表示的是传入到方法中的对象实例。
三、维护状态
我们知道 v-for 就是基于 一个数组渲染一个列表。 数组就是Vue实例中的数组,列表就是HTML中的一个元素列表。
维护状态讲的就在 v-for 指令中渲染的列表中的每一个元素所应该具有的 唯一key值 。
维护状态是啥意思?
当Vue正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。
如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素,并且确保他们在每个索引位置正确渲染。
这个类似于Vue 1.x的track-by="$index"。
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时DOM状态(如:比表单输入值)的列表渲染输出。
这是官网的原话,我们从这里知道的是,当Vue在更新(也可以说是改变)我们用v-for渲染的元素列表时,Vue会采取“就地更新”这个策略来完成更新,(这里我不知道“就地更新”是怎么实现的,以及它的为什么要这么样实现,这个要到后面填坑)。
那么要就地更新,Vue就需要知道每个节点的身份,从而能够重用和重排序现有元素(为了能够实现“就地跟新?”),这个时候就需要 key 了。
key属性为每一个元素节点提供一个唯一的“标识”。
<div v-for="item in items" v-bind:key="item.id">
<!--内容-->
</div>
建议尽可能在使用 v-for 时提供 key 属性,除非遍历输出的DOM内容非常的简单,或者是刻意依赖默认行为以获得性能上的提升。
不要使用对象或数组之类的非基本类型值作为 v-for 的 key 。请用字符串或数值类型。
# key
预期: number | string | boolean | symbol
key的特殊attribute主要用在Vue 的虚拟DOM算法,在新旧nodes对比辨识VNode。
- 如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型的元素的算法。(即不使用key,就会采取“就地更新”策略)
- 使用key时,它会基于key的变化重新排序元素顺序,并且会移除key不存在的元素。
有相同父元素的子元素必须有独特的key。重复的key会造成渲染错误。
key也可以用于强制替换元素(或替换组件)而不是重复使用它。(在前面的条件渲染中的“用管理key管理可复用的元素”中有描述)当在如下场景下这种方式可能很有用:
- 完整地触发组件的生命周期钩子
- 触发过渡
<!--这里,当text发生改变时,<span>总是会被替换而不是修改,因此会触发过渡--> <transition> <span :key="text">{{ text }}</span> </transition>
四、数组更新检测
1、变更方法
Vue将被侦听的数组的变更方法进行包裹,所以他们也将会触发视图更新。这些被包裹过的方法包括:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
如何在Vue 中使用,很简单:
//就像前面讲到的Vue实例example1
example1.items.push({ message:'Baz' });
2、替换数组
变更方法,就是会变更原数组的方法。
非变更方法——filter()、concat()、slice()等。总是会返回一个新数组。
当使用这些非变更方法时,可以用新数组替换旧数组。
example1.items = example1.items.filter(function(item){
return item.message.match(/Foo/);
});
Vue 为了实现DOM元素的最大重用而实现了一些智能的启发式方法,所以Vue不会丢弃现有的DOM并重新渲染整个列表。这样,用一个含有相同元素的数组去替换原来的数组时非常高效的操作。
(什么启发式方法?具体如何实现的?)
#注意:由于JavaScript的限制,Vue不能检测数组和对象的变化。深入响应式原理中会有相应的讨论。
五、显示过滤/排序后的结果
有些时候我们需要过滤或者排序操作一个数组,但是我们不想实际变更或重置原始数据。
# 这种情况下,我们可以创建一个计算属性,来返回过滤或排序后的数组。
<li v-for="n in evenNumbers">{{ n }}</li>
data:{
numbers:[1,2,3,4,5,6]
},
computed:{
evenNumbers:function(){
return this.numbers.filter(function(number){
return number % 2 === 0;
});
}
}
(这里我有一个疑问就是:在Vue实例中的methods、watch选项中不要使用箭头函数,因为Vue 中的这些时选项对象的this对象已经自动指向了Vue实例,而箭头函数的this指向的是父级作用域的上下文,这样会发生冲突,导致this值不会指向期望的Vue实例。那么在这里的filter()中的function()是否可以用箭头函数来简化呢?这就需要下去搞明白为什么Vue选项参数的this值已经自动指向了Vue实例)
在嵌套的 v-for 循环中,计算属性不适用,则可以适用一个方法(methods选项):
<ul v-for="set in sets">
<li v-for="n in even(set)">{{ n }}</li>
</ul>
data:{
sets:[[1,22,3,4,5],[6,7,8,9,10]]
},
methods:{
even:function(numbers){
return numbers.filter(function(number){
return number % 2 ===0;
});
}
}
六、在 v-for 中使用值范围
v-for 可以接受整数。这种情况下会将模板重复对应次数。
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
结果为:12345678910
七、在 <template> 上使用 v-for
可以利用带有 v-for 的 <template> 来循环渲染一段包含多个元素的内容。比如:
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
八、v-for 与 v-if 一起使用
注意:我们不推荐在同一元素上使用v-if和v-for。这在上一章:“条件渲染”已经提到了
要记住的是:当它们处于同一个节点时, v-for 的优先级比 v-if 的高。
这意味着, v-if 将重复运行与每一个 v-for 循环中。
# 当你想为部分项 渲染 节点时,这种优先级的机制会十分的有用。如:
<!--只渲染未完成的todo-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>
这个时候,我们在每次渲染这个列表的时候,都会去循环遍历todos。在每次重渲染的时候遍历整个列表,那我们为什么不把符合“!todo.isComplete”的todo实现放到一个数组中,然后就只用遍历这个符合条件数组,省掉多余的循环。
//这里使用computed属性,todos是我们的依赖, //在第一次计算中,我们将todos中符合条件的todo通过filter返回,并缓存 //之后只要todos发生变化,我们computed中的notComplete()才会重新计算并返回新值,再缓存 computed:{ notComplete:function(){ return this.todos.filter(function(todo){ return !todo.isComplete; }); } } /* 使用这种方式: 1、过滤后的列表只会在todos数组发生变化时才会被重新计算,过滤更加高效; 2、使用 v-for="todo in notComplete"之后,在渲染时,只遍历 未完成的todo,渲染更高效 3、解耦渲染层的逻辑,可维护性更强(对逻辑的更改和扩展更强) */ //以上就是为什么这种情况下不推荐在同一个元素下同时使用v-for和v-if
# 如果你是要有条件的跳过循环的执行,
<ul>
<li v-for="user in users" v-if="shouldShow" :key="user.id">
{{ user.name }}
</li>
</ul>
这个时候要表达的其实是,如果某个列表应该被隐藏,即 v-if 中的shouldShow的值为false,那么就不渲染。
可以看出来,这里如果某个列表的shouldShow为false,我们的v-if其实只检查一次得到这个值,就可以确定不渲染这个列表了。
但是v-for 的优先级比 v-if 高,v-if将重复运行在每一个v-for循环中。所以,虽然第一次已经确定不渲染这个列表,但是由于v-for 循环的存在,v-if还是会对列表中每个用户检查shouldShow值,然后每一次返回的都是false,这就做了多余的检测。
所以我们可以在一开始就使用这个检测,将 v-if 移动到容器元素(如:ul、ol),就只会检查shouldShow一次,且在这个值为false是不运算v-for。
所以这种情况也不推荐将v-for和v-if使用在同一个元素上。
上面的代码改为:
<ul v-if="shouldShow"> <li v-for="user in users" :key="user.id"> {{ user.name }} </li> </ul>
九、在组件上使用 v-for
# 在自定义组件上,可以使用v-for。
在2.2.0+的版本中,当在组件上使用v-for时,key 是必须的。
<my-component v-for="todo in todos" :key="todo.id"></my-component>
(真正有点意思的来了)
为什么有意思呢?我们知道,每一个自定义的组件都是一个Vue实例,它有自己独立的作用域,所以任何数据都不会自动传递到组件中,来看一看我们第一天学的“组件化构建应用”中的例子:
Vue.component("todo-item",{ prop:["todo"], template:"<li>{{ todo.text }}</li>" }); var app7 = new Vue({ el:"#app-7", data:{ groceryList:[ {id:0,text:"蔬菜"}, {id:1,text:"奶酪"}, {id:2,text:"水果"} ] } });
在这里如果我们使用这种v-for方式创见自定义的组件:
<div id="app-7"> <ol> <todo-item v-for="item in groceryList" v-bind:key="item.id"></todo-item> </ol> </div>
这里得到的结果是:啥也没有!
(这里todo-item 是能够获取到item in froceryList 的循环次数,但得不到 item 的值)
为什么?
你看,这个自定义的组件是一个单独Vue实例,而我们这里的 groceryList 是定义在app7这个Vue实例中的,在我们定义的这个自定义组件——单独的Vue实例是不具备这个数组的。在app7 Vue实例中定义的这些数据(也就是groceryList数组)不会自动传递到组件中。
怎么办呢?
我们需要一个“通道”,一个连接桥,将定义在app7实例中的groceryList 数组传递给 这个自定义组件。
这个通道就是将我们的自定义组件的"todo"属性,和 app7实例中数组的item别名相绑定。<div id="app-7"> <ol> <todo-item v-for="item in groceryList" v-bind:todo="item" v-bind:key="item.id"></todo-item> </ol> </div>
这样就可以输出正确的结果了。
(疑问1:按理说,app7实例全权控制了app-7元素,那么组件的作用域应该是在app7实例的之内,为什么组件的不能访问app7实例中的数据呢?
所以,要下去搞明白Vue实例中 el 选项绑定元素id,到底是如何实现的?上面这种情况为什么会这样?以及v-bind、v-for、v-if、v-on、v-model等的实现原理)
不自动将 item 注入到组件是因为:如果注入,会使组件与 v-for 的运作紧密耦合。明确组件数据的来源能够是 组件在其他场合重复使用。