列表渲染指令 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>

百分之一百零八
15 声望3 粉丝