一. v-for 列表渲染
1. 列表渲染
在真实开发中,我们往往会从服务器拿到一组数据,并且需要对其进行渲染
这个时候我们可以使用
v-for
来完成v-for
类似于js
的for
循环,可以用于遍历一组数据
2. v-for 基本使用
v-for
的基本格式是"item in Array"
:- 数组通常是来自
data
或者prop
,也可以是其他方式 item
是我们给每项元素起的一个别名,这个别名可以自定义
- 数组通常是来自
我们知道,在遍历一个数组的时候会经常需要拿到数组的索引:
- 如果我们需要索引,可以使用格式:
"(item, index) in Array"
- 注意上面的顺序:数组元素项
item
是在前面的,索引项index
是在后面的 - 第一个
item
其实可以利用对象的结构语法写成这样获取里面的属性:"{name, price, desc} in 数组"
html<div id="app"> <!-- 1.电影列表进行渲染 --> <h2>电影列表</h2> <ul> <li v-for="movie in movies">{{ movie }}</li> </ul> <!-- 2.电影列表同时有索引 --> <ul> <li v-for="(movie, index) in movies">{{index + 1}} - {{ movie }}</li> </ul> <!-- 3.遍历数组复杂数据 --> <h2>商品列表</h2> <div class="item" v-for="item in products"> <h3 class="title">商品: {{item.name}}</h3> <span>价格: {{item.price}}</span> <p>秒杀: {{item.desc}}</p> </div> </div> <script> const app = Vue.createApp({ data() { return { movies: ["星际穿越", "少年派", "大话西游", "哆啦A梦"], products: [ { id: 110, name: "Macbook", price: 9.9, desc: "9.9秒杀, 快来抢购!" }, { id: 111, name: "iPhone", price: 8.8, desc: "9.9秒杀, 快来抢购!" }, { id: 112, name: "小米电脑", price: 9.9, desc: "9.9秒杀, 快来抢购!" }, ] } }, }) app.mount("#app") </script>
- 如果我们需要索引,可以使用格式:
二. v-for 渲染类型
1. v-for 支持的类型
v-for
支持遍历对象,并且支持有一二三个参数:js// 1个参数 value in object // 2个参数 (value, key) in object // 3个参数 (value, key, index) in object
v-for
支持遍历数字:- 每一个
item
都是一个数字 v-for
也支持遍历其他可迭代对象(Iterable
)
html<div id="app"> <!-- 1.遍历数组 --> <!-- 2.遍历对象 --> <ul> <li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li> </ul> <!-- 3.遍历字符串(iterable) --> <ul> <li v-for="item in message">{{item}}</li> </ul> <!-- 4.遍历数字 --> <ul> <li v-for="item in 100">{{item}}</li> </ul> </div> <script> const app = Vue.createApp({ data() { return { message: "Hello Vue", movies: [], info: { name: "why", age: 18, height: 1.88 } } }, }) app.mount("#app") </script>
- 每一个
2. template 元素
类似于
v-if
,你可以使用template
元素来循环渲染一段包含多个元素的内容:- 我们使用
template
来对多个元素进行包裹,而不是使用div
来完成 Vue2
中template
只允许有一个根元素,Vue3
中允许有多个根(内部其实是通过fragment
包裹起来)
html<div id="app"> <!-- 如果div没有实际的意义, 那么可以使用template替换 --> <div v-for="(value, key, index) in infos"> <span>{{value}}</span> <strong>{{key}}</strong> <i>{{index}}</i> </div> </div> <script> const app = Vue.createApp({ data() { return { infos: { name: "why", age: 18, height: 1.88 } } } }) app.mount("#app") </script>
- 我们使用
三. 数组更新的检测
Vue
将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新不修改原数组的方法是不会被侦听
这些被包裹过的方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
替换数组的方法
- 上面的方法会直接修改原来的数组
- 但是某些方法不会替换原来的数组,而是会生成新的数组,比如
filter()
、concat()
和slice()
html<div id="app"> <ul> <li v-for="item in names">{{ item }}</li> </ul> <button @click="changeArray">修改数组</button> </div> <script> const app = Vue.createApp({ data() { return { names: ["abc", "cba", "nba", "aaa", "ccc"] } }, methods: { changeArray() { // 1.直接将数组修改为一个新的数组 this.names = ["why", "kobe"] // 2.通过一些数组的方法, 修改数组中的元素 this.names.push("why") this.names.pop() this.names.splice(2, 1, "why") this.names.sort() this.names.reverse() // 3.不修改原数组的方法是不能侦听(watch) const newNames = this.names.map(item => item + "why") this.names = newNames } } }) app.mount("#app") </script>
四. v-for 的 key 属性
在使用
v-for
进行列表渲染时,我们通常会给元素或者组件绑定一个key
属性这个
key
属性有什么作用呢?我们先来看一下官方的解释:key
属性主要用在Vue
的虚拟DOM
算法,在新旧nodes
对比时辨识VNodes
如果不使用
key
,Vue
会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法而使用
key
时,它会基于key
的变化重新排列元素顺序,并且会移除/销毁key
不存在的元素
官方的解释对于初学者来说并不好理解,比如下面的问题:
什么是新旧
nodes
,什么是VNode
?没有
key
的时候,如何尝试修改和复用的?有
key
的时候,如何基于key
重新排列的?
五. Vue 的虚拟 DOM
1. 认识 VNode 虚拟节点
我们先来解释一下
VNode
的概念:因为目前我们还没有比较完整的学习组件的概念,所以目前我们先理解为
HTML
元素创建出来的VNode
VNode
的全称是Virtual Node
,也就是虚拟节点事实上,无论是组件还是元素,它们最终在
Vue
中表示出来的都是一个个VNode
VNode
虚拟节点的本质是一个js
的对象,就是元素在js
中的一种表现形式
2. 虚拟 DOM
如果我们不只是一个简单的
div
,而是有一大堆的元素,那么它们应该会形成一个VNode Tree
本质上也就是一大堆
js
对象嵌套组成的树结构作用:
一:利用
diff
算法来进行真实dom
的更新,减少回流重绘,而且真实dom
是有附带很多东西的,相对虚拟dom
更笨重二:提供跨平台性,通过虚拟
dom
除了可以解析成在web
端运行的html
代码,还可以解析成ios端
或Android端
的原生控件- 比如
React
=>React Native
,Vue
=>Weex
- 比如
3. 插入 F 的案例
我们先来看一个案例:这个案例是当我点击按钮时会在中间插入一个
f
我们可以确定的是,这次更新对于
ul
和button
是不需要进行更新,需要更新的是我们li
的列表:- 在
Vue
中,对于相同父元素的子元素节点并不会重新渲染整个列表 - 因为对于列表中
a
、b
、c
、d
它们都是没有变化的 - 在操作真实
DOM
的时候,我们只需要在中间插入一个f
的li
即可
- 在
那么
Vue
中对于列表的更新究竟是如何操作的呢?Vue
事实上会对于有key
和没有key
会调用两个不同的方法- 有
key
,那么就使用patchKeyedChildren
方法 - 没有
key
,那么久使用patchUnkeyedChildren
方法
html<div id="app"> <button @click="insertF">插入f</button> <ul> <!-- key要求是唯一: id --> <li v-for="item in letters" :key="item">{{item}}</li> </ul> </div> <script> const app = Vue.createApp({ data() { return { letters: ["a", "b", "c", "d", "e"] } }, methods: { insertF() { this.letters.splice(2, 0, "f") this.letters.splice() } } }) app.mount("#app") </script>
4. Vue 源码对于 key 的判断

5. 没有 key 的操作(源码)

6. 没有 key 的过程如下
我们会发现上面的
diff
算法效率并不高:c
和d
来说它们事实上并不需要有任何的改动- 但是因为我们的
c
被f
所使用了,所有后续所有的内容都要一次进行改动,并且最后进行新增
7. 有 key 执行操作(源码)

六. v-for 的 diff 算法
1. 有 key 的 diff 算法如下(一)
第一步的操作是从头开始进行遍历、比较:
a
和b
是一致的会继续进行比较c
和f
因为key
不一致,所以就会break
跳出循环
第二步的操作是从尾部开始进行遍历、比较:
2. 有 key 的 diff 算法如下(二)
第三步是如果旧节点先遍历完毕,但是依然有新的节点,那么就新增节点:
第四步是如果新的节点先遍历完毕,但是依然有旧的节点,那么就移除旧节点:
3. 有 key 的 diff 算法如下(三)
第五步是最特色的情况,中间还有很多未知的或者乱序的节点:
所以我们可以发现,
Vue
在进行diff
算法的时候,会尽量利用我们的key
来进行优化操作- 在没有
key
的时候我们的效率是非常低效的 - 在进行插入或者重置顺序的时候,保持相同的
key
可以让diff
算法更加的高效
- 在没有