我们可以用v-for 指令基于一个数组来渲染一个列表。v-for指令需要使用item in items 形式的特殊语法,其中items 是源数据数组,而item 则是被迭代的数组元素的别名。
<body>
<div id='app'>
<ul>
<li v-for="item in names">{{item}}li>
ul>
div>
<script src='./js/vue.js'>script>
<script>
const app = new Vue({
el: '#app',
data: {
names: ['kobe', 'allen', 'tmac', 'carter']
}
})
script>
body>
你也可以用 of 替代 in 作为分隔符
<li v-for="item of names">{{item}}li>
<ul>
<li v-for="(item,index) in names">{{index+1}}-->{{item}}li>
ul>
当我们不使用遍历时展示数据
<body>
<div id='app'>
<ul>
<li>{{info.name}}li>
<li>{{info.age}}li>
<li>{{info.height}}li>
ul>
div>
<script src='./js/vue.js'>script>
<script>
const app = new Vue({
el: '#app',
data: {
info: {
name: '老王',
age: 18,
height: 1.88
}
}
})
script>
body>
<ul>
<li v-for='item in info'>{{item}}li>
ul>
......
data: {
info: {
name: '老王',
age: 18,
height: 1.88
}
}
<ul>
<li v-for='(value,key) in info'>{{value}}-->{{key}}li>
ul>
......
data: {
info: {
name: '老王',
age: 18,
height: 1.88
}
}
<ul>
<li v-for='(value,key) in info'>{{value}}-->{{key}}li>
ul>
......
data: {
info: {
name: '老王',
age: 18,
height: 1.88
}
}
官方推荐我们在使用v-for时,给对应的元素或者组件添加一个:key属性
为什么需要这个key呢?
这个其实和vue虚拟dom的算法有关
当某一层有很多相同的节点时,也就是列表节点,我们希望引入一个新的节点
我们希望可以把B和C之间增加一个F,Diff算法默认执行起来就是这样的:
即把C更新成F,D更新成C,E更新成D,最后插入E,是不是很没有效率,所以我们需要使用key来给每一个节点做一个唯一标识Diff算法就可以正确的识别此节点找到正确的位置去插入新的节点,所以一句话,key的作用主要是为了高效的更新虚拟dom
我们可以通过一个案例来看一下:
以下代码是一个简单的demo,功能能简单,首先是把数组里的数据遍历出来,并且填充进去,然后有一个输入框去输入数据,点击按钮的方法是删除当前数据:
<body>
<div id='app'>
<div v-for='(item,index) in list'>
<span>{{item}}span>
<input type="text">
<button @click='btn(index)'>delbutton>
div>
div>
<script src='./js/vue.js'>script>
<script>
const app = new Vue({
el: '#app',
data: {
list: [1, 2, 3],
},
methods: {
btn(index) {
this.list.splice(index, 1)
}
}
})
script>
body>
页面展示效果:
此时我们在input框中去输入1,2,3
当我们点击第二个删除按钮时:我们期望得到的结果肯定是这样的:
但是我们点击了之后的实际结果却是:
我们发现第三项被删除了,为什么呢?产生这种情况的原因是什么呢?
原因很简单,你认为你删除的是2,但是vue会认为你做了两件事:
1.把2变成了3,然后把三删除了
我们可以对比一下两个数组:
[1,2,3]=>[1,3,undefined]==>我们会说就是少了一个2
但是计算机会怎么对比数组呢?遍历
首先对比 1 和 1,发现「1 没变」;然后对比 2 和 3,发现「2 变成了 3」;最后对比 undefined 和 3,发现「3 被删除了」。
那么要如何解决这个问题呢?怎么样让vue知道我删除的是第二个,而不是第三个?用id作为key
此时我们修改一下代码:
<body> <div id='app'> <div v-for='(item,index) in list' :key='item.id'> <span>{{item}}span> <input type="text"> <button @click='btn(index)'>delbutton> div> div> <script src='./js/vue.js'>script> <script> const app = new Vue({ el: '#app', data: { list: [ { id: 1, value: 1 }, { id: 2, value: 2 }, { id: 3, value: 3 }, ] }, methods: { btn(index) { this.list.splice(index, 1) } } }) script>body>
当我们在input中输入1,2,3后,页面显示:
此时我们再次点击删除第二项:
发现得到了我们想要的结果,此时我们可以再从计算机的角度来考虑一下:
原本的数组是 [{ id: 1, value: 1 }, { id: 2, value: 2 }, { id: 3, value: 3 }]
点击删除之后的数组是 [{ id: 1, value: 1 }, { id: 3, value: 3 }]
首先对比一下:首先发现 id 从 1 2 3 变成了 1 3,说明第二项被删除了
然后依次对此 id: 1 的项和 id: 3 的项,发现没变化。
所以计算机得出结论:第二项被删除了。符合人类预期!
注意:不要使用index做key值,如果你用 index 作为 key,那么在删除第二项的时候,index 就会从 1 2 3 变成 1 2(因为 index 永远都是连续的,所以不可能是 1 3),那么 Vue 依然会认为你删除的是第三项。也就是会遇到上面一样的 bug。所以,永远不要用 index 作为 key。
当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。
如果数据项的顺序被改变,Vue 将不会移动DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。
<body> <div id='app'> <div v-for='(item,index) in arr'>{{index}}=>{{item}}div> div> <script src='./js/vue.js'>script> <script> const app = new Vue({ el: '#app', data: { arr: [1, 2, 3], } }) script>body>
直接对数据对象进行新增操作,不会立即触发网页重新渲染, 只有更改了某个值之后,才会进行渲染
<body> <div id='app'> <div v-for='(value, key, index ) in obj'>{{index}}=>{{key}}=>{{value}}div> div> <script src='./js/vue.js'>script> <script> const app = new Vue({ el: '#app', data: { obj: { name: '新木优子', sex: '女', age: '25' }, } }) script>body>
同样,当我们直接对数据执行以下操作也是没啥意义的
1.当你利用索引直接设置一个数组项时
2.当你修改数组的长度时
但是我们可以使用以下方法来实现直接对一个Vue实例内的数组项目或是对象进行操作而且还能实现触发DOM更新
target:要更改的数据源(可以是对象或者数组); key:要更改的具体数据; value :重新赋的值
<body> <div id='app'> <div v-for='(item,index) in arr'> {{index}}=>{{item}} <input type="text" @input='fn(index,$event)'> div> div> <script src='./js/vue.js'>script> <script> const app = new Vue({ el: '#app', data: { arr: [1, 2, 3], obj: { name: '新木优子', sex: '女', age: '25' }, }, methods: { fn(index, e) { Vue.set(this.arr, index, e.target.value) } } }) script>body>
<body> <div id='app'> <div v-for='(value, key,index ) in obj'> {{index}}=>{{key}}=>{{value}} <input type="text" @input='fn_obj(key,$event)'> div> div> <script src='./js/vue.js'>script> <script> const app = new Vue({ el: '#app', data: { obj: { name: '新木优子', sex: '女', age: '25' }, }, methods: { fn_obj(key, e) { Vue.set(this.obj, key, e.target.value) } } }) script>body>
我们再次添加size也能有添加操作
<div v-for='(value, key,index ) in obj'> {{index}}=>{{key}}=>{{value}} <input type="text" @input='fn_obj(key,$event)'>div><button @click='fn_add()'>addbutton>......fn_add(key, e) { Vue.set(this.obj, 'size', 36)}
Vue 将被侦听的数组的变异方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push(), pop(), shift(), unshift(), splice(), sort(), reverse()
注意:以上方法会改变vue实例里面的数组本身,这些方法也被成为变异方法
非变异 (non-mutatingmethod) 方法,例如 filter()、concat()和 slice()。它们不会改变原始数组,而总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组:
<body> <div id='app'> <div v-for="(item, index) in arr">{{index}}=>{{item}}div> <button @click='fn'>按钮button> div> <script src='./js/vue.js'>script> <script> const app = new Vue({ el: '#app', data: { arr: [1, 2, 3, 4, 5, 6, 7] }, methods: { fn() { this.arr = this.arr.slice(2, 5) }, } }) script>body>
这种重新设置数据的方式不会导致Vue 丢弃现有 DOM 并重新渲染整个列表。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
有时,我们想要显示一个数组经过过滤或排序后的版本,而不实际改变或重置原始数据。
在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组。
<body> <div id='app'> <div v-for='(value, key) in test_sort'>{{key}}=>{{value}}div> div> <script src='./js/vue.js'>script> <script> const app = new Vue({ el: '#app', data: { player: [ { num: 24, name: 'kobe', test: 81 }, { num: 3, name: 'iverson', test: 60 }, { num: 1, name: 'tmac', test: 62 }, { num: 15, name: 'carter', test: 50 }, ] }, computed: { test_sort() { return this.player.filter((item) => { return item.test > 60 }) } } }) script>body>
在方法中也可以实现:
<body> <div id='app'> <div v-for='(value, key) in test_sort()'> {{key}}=>{{value}} div> <script src='./js/vue.js'>script> <script> const app = new Vue({ el: '#app', data: { player: [ { num: 24, name: 'kobe', test: 81 }, { num: 3, name: 'iverson', test: 60 }, { num: 1, name: 'tmac', test: 62 }, { num: 15, name: 'carter', test: 50 }, ] }, methods: { test_sort() { return this.player.filter((item) => { return item.test > 60 }) } } }) script>body>
v-for也可以接受整数。在这种情况下,它会把模板重复对应次数。
<div id='app'> <div v-for='n in 10'>{{n}}hello worlddiv>div>
不要在同一元素上使用 v-if 和 v-for,当它们处于同一节点,v-for的优先级比v-if 更高,这意味着v-if 将分别重复运行于每个
v-for 循环中。当如果你的目的是有条件地跳过循环的执行,那么可以将v-if 置于外层元素
<body> <div id='app'> <div v-for='(item,index) in colors' v-if=flag> {{item}} div> div> <script src='./js/vue.js'>script> <script> const app = new Vue({ el: '#app', data: { colors: ['red', 'blue', 'green'], flag: true } }) script>body>
以上代码,虽然现实都没有问题,但是他的执行顺序是啊先进行v-for循环遍历,然后再进行v-if的判断是否加载显示,这是存在很大的资源浪费的,因为我们期望的结果是,如果v-if的条件是false,那么我们就不进行循环,这样才是效率最高的方法.
此时我们可以将if嵌套的外面,同样可以达到目的,此时如果v-if的条件是false那么久直接不进行遍历渲染
<div v-if=flag> <div v-for='(item,index) in colors'> {{item}} div>div>
上面的案例是v-for在里面,那么v-for在外面,v-if在里面也是可以的
<div v-for='(item,index) in colors'> <div v-if='index % 2'> 奇数=>{{item}} div> <div v-else> 偶数=>{{item}} div>div>
我们先以生活中的例子来举例,比如当我们遇到复杂问题时,我们的处理方式是什么,比如现在年轻人常见的问题=>结婚,买房,买车…我们任何一个人处理问题的能力都是有限的,所以当我们一次性面对多个问题时,我不不太可能搞定一大堆内容,但是我们人又一中天生的能力,就是对问题进行拆解,如果将这些复杂的问题,拆分成很多个可以处理的小问题,那么当把所有的小问题都处理完成之后,大问题想对应的也就破解了,比如找对象,买房,买车我们一次性解决这三个问题是很困难的,除非你有钞能力,但是我们可以一个一个解决,比如,改变自己,换风格,改穿搭,可以找到对象,找到对象后,可能老丈人给你支持一点,你就可以买房,在自己工作2年就可以买车,那么这个大问题就被解决了.
组件化也是类似的思想
如果我们讲一个页面中多有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理及扩展,但是如果我们将一个页面拆分成一个个小的功能模块,每个功能模块完成属于自己的这部分的独立功能,那么之后整个页面的管理和维护就变得非常容易了
组件化是vue.js中的重要思想
它提供了一种抽象,让我们可以开发出一个个独立的可复用的小组件来构造我们的应用
任何的应用都会被抽象成一颗组件树
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
组件是可复用的Vue 实例,且带有一个名字,会把HTML相关的代码直接整合到实例对象中,可以实现,一个实例多处应用
Vue.component('myplay', { data: function () { return { player: [ { num: 24, name: 'kobe', test: 81 }, { num: 3, name: 'iverson', test: 60 }, { num: 1, name: 'tmac', test: 62 }, { num: 15, name: 'carter', test: 50 } ] } }, template: "- {{ball.num}}号是{{ball.name}}职业生涯最高分{{ball.test}}
",})
<div id='template_demo'> <myplay>myplay>div>
const app = new Vue({ el: '#template_demo',})
<div id='template_demo'> <myplay>myplay> <myplay>myplay> <myplay>myplay>div>
component(组件名称, 组件数据, 模板)
组件名称: 后期利用该名称在网页中是想自定义组件展示
组件数据: 组件的数据属性必须是一个函数因此每个实例可以维护一份被返回对象的独立的拷贝:如果没有这条规则,那么所有组件共享同一数据, 这就会造成所有组件的状态都是一样的
模板: 这里面存储着后面我们要设置的组件的HTML结构, 可以直接在HTML结构里面设置好各种数据和事件绑定
<body> <div id='template_demo'> <myplay>myplay> <myplay>myplay> <myplay>myplay> div> <script src='./js/vue.js'>script> <script> Vue.component('myplay', { data() { return { player: [ { num: 24, name: 'kobe', test: 81 }, { num: 3, name: 'iverson', test: 60 }, { num: 1, name: 'tmac', test: 62 }, { num: 15, name: 'carter', test: 50 } ] } }, template: "- {{index}}{{ball.num}}号是{{ball.name}}职业生涯最高分{{ball.test}}
", methods: { fn(index) { console.log(this.player[index]); this.player.splice(index, 1) } } }) const app = new Vue({ el: '#template_demo', }) script>body>
##全局组件和局部组件
全局组件
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。至此,我们演示的组件都只是通过 Vue.component
全局注册的:
Vue.component('my-component-name', { // ... options ...})
全局注册的组件可以用在其被注册之后的任何(通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。
<body> <div id='template_demo'> <myplay>myplay> <myplay>myplay> <myplay>myplay> div> <div id='app'> #app<myplay>myplay>#app div> <script src='./js/vue.js'>script> <script> Vue.component('myplay', { data() { return { player: [ { num: 24, name: 'kobe', test: 81 }, { num: 3, name: 'iverson', test: 60 }, { num: 1, name: 'tmac', test: 62 }, { num: 15, name: 'carter', test: 50 } ] } }, template: "- {{index}}{{ball.num}}号是{{ball.name}}职业生涯最高分{{ball.test}}
", methods: { fn(index) { console.log(this.player[index]); this.player.splice(index, 1) } } }) const app = new Vue({ el: '#template_demo', }) const vm = new Vue({ el: '#app', }) script>body>
局部组件
<body> <div id='template_demo'> <myplay>myplay> <demo>demo> div> <div id='app'> <myplay>myplay> <demo>demo> div> <script src='./js/vue.js'>script> <script> Vue.component('myplay', { data() { // return obj_player return { player: [ { num: 24, name: 'kobe', test: 81 }, { num: 3, name: 'iverson', test: 60 }, { num: 1, name: 'tmac', test: 62 }, { num: 15, name: 'carter', test: 50 } ] } }, template: "- {{index}}{{ball.num}}号是{{ball.name}}职业生涯最高分{{ball.test}}
", }) const demo = new Vue({ el: '#template_demo', components: { demo: { template: "hello
" } } }) const app = new Vue({ el: '#app', data: { } }) script>body>