列表渲染指令 v-for
一、指令功能
遍历源,生成与源子项数量同等的标签,并且每个标签放置一个形参用以套取源对应的子项以供本标签使用。
<li v-for="item in ['text',10]">{{item}}</li>
如上,遍历源['text',10],源有两个子项,生成与子项数量同等的2个标签。每个标签都有一个形参item,第一个标签的item形参套取出源对应的子项的值'text'以供本标签文本插值使用,第二个标签的item形参套取出源对应的子项的值10以供本标签文本插值使用。渲染后的结果如下:
二、书写规范
v-for="(item,name,index) in items"
1、形参
- 指令值有三个形参
第一形参套取的子项值,第二形参套取子项的属性名,第三形参套取的子项索引号(索引号从0开始)。
当只有第一形参时,()小括号可省略。 - 数组源的形参
当源是数组时,没有第三形参,第二形参是子项索引号。
没有套取到值的形参不会返回undefined值,而是一个空字符串。 - 第一形参套取的是子项值不是子项
对象子项包括子项名,子项值,子项ID。数组子项包括子项值,子项ID。 - 形参的作用
形参的目地就是为了从源套取值以供标签内其它属性(包括文本插值)或指令(注意指令的优先级,指令要晚于v-for才能使用到形参)。
还可以供下级子孙节点使用。详见后面章节--形参的使用。
2、源
- 源的类型
源为数组或对象,非数组或对象的源会转换为数组。
字符串: 英文每个字母为子项,中文每个单字为子项。
数字: 只参是正整数(非正整数报错),从1开始为子项,每次加1。
布尔、null、undefined: 移除本标签
变量: 由变量值决定,变量类型的值不需在引号内加引号。
函数: 以函数返回值决定,函数类型的值要在函数名后加小括号以调用的形式才会生效。
<li v-for="item in 'text'"> //字符串源(要加单引号)
<li v-for="item in 10"> //数字源
<li v-for="item in ['text',10]"> //数组源
<li v-for="item in {text:10,wow:5}"> //对像源
<li v-for="item in ['text',{tt:10,wow:5}]"> //对像数组子项相反源
<li v-for="item in items"> //变量源(不要加单引号)
<li v-for="item in items()"> //函数源(要后带括号)
三、形参的使用
(一)形参解构
请先了解JS解构。
解构就是把对象内的子项赋给新变量的过程。
v-for中如果第一形参的值是数组或对象,可以对其解构。
1、v-for解构对象是子项的值
v-for 解构的是第一形参的值,不是源,也不是子项。
未能解构的变量,会赋值undefined。如下:
<template>
<li v-for="{wo,yt} in wocao">
{{'有mes吗--'+wo}}{{'。有childIt吗--'+yt}}
</li>
</template>
<script setup>
import { ref} from 'vue'
const wocao = ref([{wo:'mes',w:'ms'},{yt:'childIt',y:'chiIt'}])
</script>
2、书写规范
子项值是对象时: v-for="({a,b},x,i) in wocao"
变量a,b要与对象的属性名匹配,未匹配到的变量会赋值undefined。
子项值是数组时: v-for="([a,b],x,i) in wocao"
可用占位法[,,c]
解构数组的某一项值,还可用[,...c]
的方式将后面的多个成员解构为新的数组(注:...的写法只能在解构后面的成员使用)。未匹配到的变量会赋值undefined。
{}、[]错用
对象解构用[]或数组解构用{},用错符号后台会跳出无法读取警告。
对非对象或数组解构,否则后台会跳出无法读取警告。
(二)形参作为子v-for嵌套源
for中有for构成嵌套。
子for可以使用外来源。
子for还可以使用父的形参为源,如下分别是使用外来源与使用父形参作为源:
<template>
<ul v-for="(x) in wocao">
{{x}}
<li v-for="(y) in x">{{y}}</li>
</ul>
<HR> </HR>
<ul v-for="(x) in wocao[0]">
{{x}}
<li v-for="(y) in wocao[1]">{{y}}</li>
</ul>
</template>
<script setup>
import { ref} from 'vue'
const wocao = ref([{message:"tuy",op:5},{childItem:"tuyti",ee:6,ggg:95}])
</script>
(三)形参作为指令值
1、能使用形参的指令
v-text与v-html 文本指令
v-if与v-show 条件渲染/显示指令
v-bind 属性绑定指令
v-for 列表渲染指令
v-on 事件处理指令
2、特别注意
v-model
v-model表单属性绑定指令不能使用v-for形参
v-if
v-if 比v-for 优先级更高,v-if 赋值时v-for 的形参还没有定义,v-if 赋未定义的变量会跳出警告。解决方法是把v-if放在v-for的子标签上。如下:
<template>
<button v-on:click="addNew">隐藏第三条</button>
<ul v-for="(todo, index) in todos">
<div v-if="todo.ww">{{todo.title}}</div>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const todos = ref([
{title: 'Do the dishes',
ww:true },
{title: 'Take out the trash',
ww:true },
{title: 'Mow the lawn',
ww:true }])
function addNew() {todos.value[2].ww=!todos.value[2].ww}
</script>
(四)形参在组件上的传递
组件传递v-for形参和传递其它变量是一样的。
把形参赋值给一个自定义的绑定属性,在子组件中使用defineProps(['XXX'])接收传来的自定义属性。如下自定义事件要用defineEmits(['YYY'])接收:
父
<template>
<HelloWor :XXX="a" @YYY="b(index)" v-for="([a,b], index) in yy"/>
</template>
<script setup>
import HelloWor from './components/HelloWorld.vue'
import {ref} from 'vue'
const yy=[[3,(tt)=>{console.log(tt)}]]
</script>
子
<template>
<li>
{{XXX}}
<button @click="$emit('YYY')">Remove</button>
</li>
</template>
<script setup>
defineProps(['XXX'])
defineEmits(['YYY'])
</script>
四、列表操作
当v-for绑定的源是响应式变量时,列表会随变量改变而更新。
通过对源的操作(如增、删、改、排序)可实现对列表的操作。
数组有丰富的函数用于操作数组,因此把v-for的源最好是设置为数组变量。
1、数组函数基础
数组函数分为
可变数组函数(会改变数组): 增、删、改、插、颠倒、排序。
不变数组函数(不会改变原数组): 过滤、处理、拼接、抽取。
数组查询函数: 返回真假、返回KEY、返回值。
a、可变数组函数
- 增:Arr[X]="YYY" 当X的值=数组长度时,可通过赋值的方式在数组最后增加一项值。
- .push("XXX")在数组最后增加一项或多项值,返回数组新的长度(未知数组长度的办法)。
- .unshift("XXX") 在数组最前增加一项或多项值,返回数组新的长度。
- 删:delete Arr[X] 删除数组指定索引的值,返回true或false(只删除值,并不删除此项,只是值为空,数组长度不变)
- .pop() 删除数组最后一项,返回删除的值(数组减1)
- .shift() 删除数组最前一项,返回删除的值(数组减1)
- XX=[] 全删(数组为0项)
- 改:Arr[X]="YYY" 通过赋值更改已知索引项的值
- 插删:.splice(index,X,'Y'...) 从索引号“index"开始删除"X"个值,并插入"Y"或多项值,返回删除的数组,删除会致删除位后的数组元素向前移动一位,要注意索引号的改变。
第二个参数"X"为0为不删除,缺省为从“index"开始全删。
第三个参数为插入的值,插入时,第二个参数不可省。 - 颠倒:.reverse() 颠倒数组中元素的顺序(只颠倒改序不排序),返回颠倒顺序后的数组
- 排序:.sort() 不带参数为对字符升序(数字也会转成字符串),返回升序后的数组。降序要在后面加上reverse() 方法
- .sort(function(a,b){return a-b}) 带参数可对数字升序排列,降序排列function(a,b){return b-a}
- .sort(function(a,b){return a[3]-b[3]}) 二维数组通过加二维下标的方式排序,降序排列function(a,b){return b[3]-a[3]}
- .sort(function(a,b){return a[3].charCodeAt()-b[3].charCodeAt()}) 字符形二维数组可用.charCodeAt()把字符串转成十进制ASCII码来排序
b、不变数组函数
- 过滤:.filter(function (x,y,z) { return x==0}) 通过回调函数检查原数组的每项值,返回符合条件的元素(元素不变,元素个数变)。参数x是每项的值,y是索引,z是数组本身。
- 处理:.map(function (x,y,z) { return x++}) 通过回调函数处理数组的每项值,返回处理后的数组(元素变,元素个数不变)。参数x是每项的值,y是索引,z是数组本身。
- 拼接: A.concat(B数组,C数组......) 用于连接两个或多个数组
- 抽取:.slice(index,X) 从索引号开始到第X个止,取出. 注:index是从0开始的索引号,X是从1开始的数组序号。
c、数组查询操作
- indexOf :判断数组中是否存在某个值,如果存在,则返回数组元素的下标,否则返回-1;
- includes :判断数组中是否存在某个值,如果存在返回true,否则返回false;
- find :返回数组中满足条件的第一个元素的值,如果没有,返回undefined;
- findeIndex :返回数组中满足条件的第一个元素的下标,如果没有找到,返回-1
2、操作数组对列表的影响
列表与响应式数组同步,通过对数组的增、删、插、颠倒、排序、过滤、拼接、抽取可对列表进行增、删、插、颠倒、排序、过滤、拼接、抽取。
需注意的是不变数组函数(过滤、拼接、抽取)需要将函数的返回值赋给原数组才能使列表改变。如下,filter过虑后的返回值重新赋给wocao:
<template>
<li v-for="(x,y) in wocao">
{{x}}
</li>
<button @click="pp">测试按钮</button>
</template>
<script setup>
import { ref,computed} from 'vue'
const wocao = ref(['你','我','a','b'])
const pp=function(){wocao.value=wocao.value.filter(function (x,y,z) { return x=='a'||x=='b'}) }
</script>
3、操作数组子项值对列表的影响
数组的增、删、插、颠倒、排序、过滤、拼接、抽取对列表的影响是直接的。
数组对子项的改、处理对列表的影响是间接的,他的影响与列表如何使用数组子项有关。
a、文本插值时的影响
当文本插值=undefined时,有些标签宽度将为0。标签宽度为0的标签不会被显示。
改变数组子项值使第一形参值为undefined,或v-for解构的变量为undefined来达到选择性显示列表的目地。如下是利用使第二项值解构为undefined来使第二项不显示的目地。(<div>
有0宽度特性,li标签无0宽度特性)
<template>
<div v-for="{wo} in wocao">
{{wo}}
</div>
</template>
<script setup>
import { ref} from 'vue'
const wocao = ref([{wo:'mes'},{yt:'childIt'}])
</script>
b、作为其它指令值时的影响
数组子项值为v-if与v-show指令值,或v-for解构的变量为v-if与v-show指令值时可以控制列表的显示。
数组子项值为v-bind指令值,或v-for解构的变量为v-bind指令值时可以控制列表的属性(包括显示属性)。
4、为列表制定规则
可通过为数组制定规则使列表按照规则显示。如使数组改变时列表总是升序或降序、或对数组改变进行过滤只限时过滤后的列表。这就需要有函数能侦测数组的改变和处理。
计算属性侦测处理数组
如下是通过计算属性对数组变化过滤,只显示文本插值为0或1的列表:<template> <li v-for="(x,y) in mabi"> {{x}} </li> <button @click="pp">测试按钮</button> </template> <script setup> import { ref,computed} from 'vue' const wocao = ref(['你','我','a','b']) const mabi = computed(() => { return wocao.value.filter(function (x,y,z) { return x==0||x==1}) }) const pp=function(){wocao.value[0]='0'} </script>
调用函数侦测处理数组
除了计算属性,还可通过调用函数来侦测处理数组,如下是通过调用函数来使数组子项为undefined时文本插值不为undefined,使列表永不出现0宽度:<template> <div v-for="(x,c) in pp()" > {{x}} </div> <div v-for="(x,c) in wocao" > {{x}} </div> <button @click="po">测试按钮</button> </template> <script setup> import { ref} from 'vue' const wocao = ref(['rt']) const pp=function(){return wocao.value.map((x)=>{return x+''})} const po=function(){wocao.value[0]=undefined} </script>
注意:函数源要带(),要return返回值给方法.
使用computed还是调用函数
computed与调用函数都能侦测处理数组,达到为列表制定规则的目地。但调用方法能够传参,当在v-for子嵌套里制定子列表规则时,如果父参为源,需要侦测处理父参时,只能使用调用函数的方式,如下子列表永远升序:<template> <div v-for="(x,c) in wocao"> <div v-for="(t,c) in pp(x)"> {{t}} </div> </div> <button @click="po">测试按钮</button> </template> <script setup> import { ref} from 'vue' const wocao = ref([[1,2],[]]) const pp=function(i){return i.sort() } const po=function(){wocao.value[0]=[3,2]} </script>
五、v-for列表更新与就地更新
1、v-for列表更新
当源更新时,所有的列表项都会更新,v-for更新特性有如下特性:
a、涉源全初始
涉源属性会依源初始化,涉源属性当前值无法保留。(文本属性不会被初始)
b、非源不初始
非源属性当前值不会被依源初始化,当前值能保留。
如下:改变列表第一项的非源属性disabled、涉源属性color的值,当源第二项更新时,涉源属性color依源初始化为'red',当前值'blue'无法保留。非源属性disabled不会被依源初始化,当前值能保留。
<template>
<button v-for="(x,c) in wocao" :id="x.color" :style="x" >我是老大 </button><br>
<button @click="pk">列表一的非源属性disabled、涉源属性color改变</button>
<button @click="po">源第二项更新</button>
</template>
<script setup>
import { ref,nextTick} from 'vue'
var el
nextTick(() => {el=document.getElementById("red")})
const wocao = ref([{color:'red'},{color:'blue'}])
const pk=function(){el.style.color='blue',el.disabled=true}
const po=function(){wocao.value[1].color='black'}
</script>
2、v-for就地更新
当源顺序改变时,v-for并不会用移动过来的DOM替换现有DOM重新渲。而是用源值替换现有DOM属性值,DOM并不移动,称之为就地更新。v-for就地更新有如下特性:
a、源值无效不替
源值替换涉源属性时,如果源值无效果,涉源属性不会被替换成默认值,涉源属性依然使用原值。
b、无源不替
源无值替换非源属性值,非源属性依然使用原值。
如下:改变现有DOM的非源属性disabled、涉源属性color的值,当插入使源第一项值改变时,并没有插入新的DOM,而是用源值替换现有DOM的属性,源值color=‘0’无效果,涉源属性不会被替换成默认值‘黑色’,涉源属性依然使用原值‘蓝色’,源无值替换非源属性disabled值,非源属性依然使用原值“禁用”。
<template>
<button v-for="(x,c) in wocao" :id="x.color" :style="x" >我是老大 </button><br>
<button @click="pk">列表改变非源属性disabled、涉源属性color</button>
<button @click="po">源插入一项值</button>
</template>
<script setup>
import { ref,nextTick} from 'vue'
var el
nextTick(() => {el=document.getElementById("red")})
const wocao = ref([{color:'red'}])
const pk=function(){el.style.color='blue',el.disabled=true}
const po=function(){wocao.value.unshift({color:'0'})}
</script>
3、更新副作用
列表更新的 涉源全初始 与就地更新的 源值无效不替、无源不替 特性有极大的副作用。
a、更新时:当前值无法保留
如上涉源全初始下的例子,列表更新时涉源属性当前值无法保留。
解决方案
涉源属性只通过源的方式变更当前值,这样就使得涉源属性当前值与源初始值永远一致,依源初始化后还是当前值。
b、就地更新时:不会替换值
如上源值无效不替 下的例子,就地更新时,源值无效涉源属性值不会替换,非源属性值不会替换。
解决方案
- 源值规范书写: 源赋值时要严格按照所涉属性的值规范书写,保证涉源DOM属性值有效。这可以避免源值无效不替的副作用。
- 使用key: 使用KEY会用虚拟DOM属性值替换现有DOM属性值,而不是用源替换现有DOM属性值。这就避免了源值无效不替和无源不替的副作用。
六、key属性
1、源DOM与虚拟DOM
源DOM: v-for使用源来映射真实的DOM组,通过操控源来更新DOM组,这个源可以称之为真实DOM组的源DOM。
虚拟DOM: VUE对于同一级的DOM使用了虚拟DOM的概念,虚拟DOM是一个js对象,映射真实的DOM组,通过操控虚拟DOM来更新真实的DOM组。
虚拟DOM的意义: 真实的DOM对象有几百条属性,当顺序改变时重新渲需要大量的运算,而虚拟DOM对象只有几条属性,当真实DOM顺序需要改变时并不真的移动和重渲需DOM对象,而是用虚拟DOM值替换现有DOM属性值。
虚拟DOM的运行机制: 同一平层的各真实DOM初始时会生成一个包含自身初始属性的虚拟DOM对象,虚拟DOM与真实DOM通过相同的key来建立链接,同一平层的虚拟DOM组成一个虚拟DOM组。
当对真实DOM属性更改时,会先更新虚拟DOM再更新真实DOM。
源DOM与虚拟DOM的区别: 源DOM与虚拟DOM都不是真实的DOM,都只包含真实DOM有限的属性,但源DOM与虚拟DOM有如下区别:
- 源DOM能直接更改,虚拟DOM只能被动更改;
- 源DOM不会记录真实DOM的更改,虚拟DOM会记录真实DOM的更改;
- 源DOM通过相同的index映射真实DOM,源DOM的index值不固定,会因源DOM在组中的位置而改变;虚拟DOM通过相同的key映射真实DOM,虚拟DOM的key值可以固定,固定的key值不会随位置而改变。
2、key在v-for中的作用
v-for使用key在就地更新中就不会使用源DOM更新而使用虚拟DOM更新,因为虚拟DOM会如实的记录真实DOM的更改,在就地更新时会用真实DOM属性值替换现有DOM属性值。避免了源值无效不替、无源不替副作用。
在给key赋值时,不要使用源的index值(因为源的index值不固定),源中用于给key赋值的属性值不得更改,最好是专用于key。使用如下:
在每个输入框中输入内容,点击在前添加按钮,虚拟DOM“key=4”会替换最前面的真实DOM属性值,包括子标签input的value属性(注意与使用源DOM的不同,源中没有input的value属性,不会被替换,而虚拟DOM有input的value属性,替换成功,避免了源就地更新的副作用)。
<template>
<h2>人员列表</h2>
<button @click="add">添加老刘</button>
<ul>
<li v-for="(p,index) in persons" :key="p.id">
{{p.name}}-{{p.age}}
<input type="text" >
</li>
</ul>
</template>
<script setup>
import { ref} from 'vue'
const persons= ref([
{'id':'001', 'name':'张三','age':'18'},
{'id':'002', 'name':'李四','age':'19'},
{'id':'003', 'name':'王五','age':'20'}
])
const add=function(){persons.value.unshift({'id':'004', 'name':'老刘','age':'40'})}
</script>
3、key的一些特性
- key标识出现在vue书写的标签中,但他并不是DOM属性,不会出现在渲染出来的真实DOM中,用js方法不能调出key属性。
- 可通过改变绑定变量的方法改变key值,当key值改变后会删除原key值的虚拟DOM,新建立一个虚拟DOM,新建立的虚拟DOM只有初始的真实DOM属性,所有更改会被只有初始属性的虚拟DOM刷新,不再保留,真实DOM回到初始状态。(利用此特性通过key值可以达到刷新DOM的目地)
详见Vue中key的作用及原理
下例是没有v-for源改变列表顺序的繁锁代码
<template>
<div>
<input value="我是一">
<button>我是二</button>
</div>
<button @click="po">改变列表顺序</button>
</template>
<script setup>
const po=function(){var arr=[]; for(var i=0;i<2;i++){
arr.push(document.body.children[0].children[1].children[i])
};arr.reverse();for(var i=0;i<arr.length;i++){
document.body.children[0].children[1].appendChild(arr[i])
}}
</script>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。