条件渲染
v-if
用于条件性地渲染一块内容,这块内容只会在指令的表达式返回真值的时候被渲染,如果返回假值就不渲染(假值包括false null undefined 0 "" 除了他们之外的值都是真值)。
切换多个元素:
因为v-if是一个指令,所以必须将它添加到一个元素上。那么如果想要同时切换多个元素,就必须要用一个父元素把多个子元素一起包裹起来,然后在父元素上设置v-if。这个时候为了不增加无用的dom元素,我们可以使用<template>元素(他是一个不可见的包裹元素),并在上面使用v-if,最终的渲染结果将不包含<template>元素。
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
当一个元素为假值的时候,dom中不会出现这个元素,原来元素的位置会被注释语句<!---->替换。
v-else
使用的时候,前面一个兄弟元素必须有v-if或v-else-if,否则会失效,整个元素都不显示了。
<div v-if="Math.random() > 0.5">
小饼
</div>
<div v-else>
你看不见小饼啦
</div>
v-else-if
可以链式调用,但是要求前面一个兄弟元素必须有 v-if 或 v-else-if
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
v-show
根据表达式的真假值,切换元素CSS中的display属性。
<h1 v-show="ok">Hello!</h1>
他不需要操作dom,虽然他也会触发重绘和重排,但是他对比v-if会少消耗一些性能。也就是说,当一个元素要被频繁切换为显示或隐藏的时候,就用v-show。
v-if与v-show的区别
-
v-if 是惰性的,如果在初始渲染时条件为假,则什么也不做。直到条件第一次变为真时,才会开始渲染元素。所以他的初始渲染开销比较小。
v-show则不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
-
v-if 有更高的切换开销,v-show 有更高的初始渲染开销(因为一个元素不管会不会显示都会被渲染)。如果需要非常频繁地切换,则使用v-show较好。如果在运行时条件很少改变,则使用v-if较好,这样能在初始渲染的时候更快一点。
-
v-show不支持
<template>元素,因为VUE编译之后形成的HTML代码中并不存在<template>标签,我们给他设置style并没有任何意义,对子元素也起不到任何的作用。 -
v-show不支持v-else/v-else-if,他们不是一对的。
v-bind 指令
动态地绑定一个或多个特性。由于指令都可以使用data里面的数据,所以也就相当于把标签的特性和data进行绑定,这时候特性就可以动态的使用data中的数据。
基本使用
<div id = "app">
<div a = "type" b = "xx" c = "ss"></div>
</div>
const vm = new Vue({
el: "#app",
data: {
type: "D"
}
})
我们希望a中的值是type的值,但是这样写a中的值只会被当成一个字符串"type",而不会被编译成"D",因为a被当成一个普通的特性。如果我们能够把他当做一个特殊的特性(vue的指令),让vue去根据这个指令进行特殊的解析和渲染就好了。这时候就需要使用bind去动态的绑定特性,冒号":"后是传递的参数。
<div id = "app">
<div v-bind:a = "type" b = "xx" c = "ss"></div>
</div>
这样设置之后,"type"就等于"D"了。
扩展:区分属性和特性
property翻译为属性,attribute翻译为特性。
举个例子:
<input id="name" value="ceshi" haha="luanxiede"/>
在这个<input>上,我们定义了id,value,haha三个特性。当浏览器解析到这个标签的时候,会把他解析成DOM对象,更准确一点说是解析为 HTMLInputElement 对象。我们看看他的继承关系:
通过查看文档会发现,HTMLInputElement的原型上定义了很多属性和方法,例如form name type alt checked src value 等等,还有从HTMLElement继承来的id title 等等。像这种input天生就带有的东西,他们既是特性,也是属性。
我们打印一下input,就可以看到他的property。如果打印input输出的是标签的形式,试试变成数组的形式再打印,如:
//直接输出:
console.log(in1) //<input id="name" value="ceshi" haha="luanxiede"/>
//用数组包裹起来
console.log([in1]) // [input#name]
我们展开property,可以看到有很多自带的属性,包括上面我们说的HTMLInputElement的原型上的属性还有一些继承的属性,
其中id和value的值就是我们写在标签里面的值,但是标签里面的特性和这里面的属性并不是一样的东西哦!(要是一样我也不会写这么多了)这只是说明当浏览器解析网页时,将HTML特性映射为了DOM属性。(这里我只截取了部分,所以你们看不到id和value,我只是想让你们感受一下property里面真的有很多乱七八糟的东西哈哈哈哈):
再沿着原型链往上看,Element类还有一个attributes属性,打开看一看:
发现里面包含了在标签中定义的所有特性,而且是按顺序乖乖排好的。但是他们又没有我们看起来的那么简单,打开每个对象,会发现里面都是一大串属性(随便截的图,感受一下就好)。
所以他们并不是简单的对象,而是Attr类型的对象,并且打印他们会得到"属性名+属性值"格式的字符串,例如:
in1.attributes.id //id="div1"
Attr类型使用对象来表示一个DOM元素的属性,继承关系如图:
这种类型很少会用到,百度了一下也没有看到什么文章是讲这个的。而且它怪怪的,一下子又继承自Node,一下子又想不继承了(目前还是继承的)所以我们简单了解一下就好。
好啦,接下来我们分开介绍一下。
attribute
特性(attribute):标签身上的都是特性,包括后天形成的。
关于特性的一些api,都定义在Element上:
- Element.hasAttribute(name) – 判断某个特性是否存在
- elem.getAttribute(name) – 获取指定特性的值
- elem.setAttribute(name, value) – 设置指定特性的值
- elem.removeAttribute(name) – 移除指定特性
div.setAttribute("haha",5) //<div haha="5"></div>
div.getAttribute("haha") //5
特性包含属性,非属性的特性包括,data cst log times等等自行设定的属性。
如果是非属性的特性,使用dom.xxx的方式赋值不会显示在标签中。
div.aaa=1 //<div></div>
如果是非属性的特性,写在行间的时候,不能通过"."的方式获取。
<div hhh="1"></div>
div.hhh //undefined
div.getAttribute("hhh") //1
如果是非属性的特性,用setAttribute赋值的时候,也不能通过"."的形式获取设置的属性,只能通过get。
div.setAttribute("hhh",3)
div.hhh //undefined
div.getAttribute("hhh") //3
property
属性(property):属性是先天有的,也算是特性。如id type value checked。
设置和获取方式:通过dom.xxx去设置和获取。
如果是属性,使用dom.xxx的方式赋值会显示在标签中。
div.id="1" //<div id="1"></div>
如果是属性,能通过"."的方式获取。
<div id="1"></div>
div.id //1
如果是属性,用setAttribute赋值的时候,能通过"."的形式获取设置的属性。
div.setAttribute("id",1)
div.id //"1"
总结和一些小细节:
-
使用setAttribute相当于直接在行间赋值,使用"."相当于给dom对象赋值,只有当属性为特性的时候两者才能互相映射。
-
只要是
setAttribute设置的值,不管设置的是什么值都会被转换为字符串,所以用set Attribute设置hidden的时候porperty是true。但是property上的值可以是任意合法的字符串。a.hidden = true //a标签消失 a.hidden = false //a标签出现 a.setAttribute("hidden",true) //a标签消失 a.setAttribute("hidden",false) //a标签消失 a.hidden //true <a href="www.baidu.com" hidden="false">baidu</a>使用getAttribute,当attribute不存在的时候会返回null。
-
访问href返回的情况不同。
<a href="www.baidu.com"></a> a.href //"file:///D:/front/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/www.baidu.com" a.getAttribute("href") //"www.baidu.com"访问特性会返回html设置的值,访问属性会返回解析后的完整url。
-
当访问和设置value的时候:
el.setAttribute('value', 'jjc'); // 修改特性 el.value === 'jjc' // 属性也更新了 el.value = 'newValue'; // 修改属性 el.getAttribute('value')) === 'jjc' // 特性没有更新当修改特性时,属性也会更新;但是修改属性后,特性却还是原值。所以value的同步是单向的,只有特性能映射属性。
-
根据 HTML 规范,标签以及特性名是不区分大小写的,写了大写也会变成小写。
如果在attribute的API中写了大写,会自动转换为小写。但是在property中会严格区分大小写:
a1.ID = "id1" console.log(a1.id); // undefined console.log(a1.attributes.id); // undefined console.log(a1.ID); // id1 console.log(a1.attributes.ID); // undefineda2.setAttribute('ID','id2'); console.log(a2.id); // id2 console.log(a2.attributes.id); // id="id2" console.log(a2.ID); // undefined console.log(a2.attributes.ID); // id="id2",因为ID会被转换为小写,实际上执行的是a2.attributes.id
回到正题来,继续讲vue的指令。
动态特性名
如果我们想让特性的名字也是动态的,这时候不能直接把特性的名字写成data中的特性名, 而是要使用方括号,如下:
<div id = "app">
<div v-bind:[attr] = "type" b = "xx" c = "ss"></div>
</div>
const vm = new Vue({
el: "#app",
data: {
type: "D",
attr: "a"
}
})
//这样设置后就变成了<div v-bind: a = "D" b = "xx" c = "ss"></div>
但是这个动态特性名是有版本要求的,他要求vue版本要在2.6.0+。
缩写
要绑定的特性一多起来,看起来就很冗余,所以vue还给我们提供了一个缩写":"。
<div :[attr] = "type" :b = "xx" :c = "ss"></div>
一下子绑定多个特性
有了缩写看起来还是冗余咋办呀,这时候可以把这些特性一起放在对象里面,VUE会把对象里面的键值对当做一对一对特性,放到dom元素里面去。
<div v-bind = "{ name: xx, age: '17', height: '163cm' }"></div>
<div v-bind = "{ id: someProp, 'other-attr': otherProp }"></div>
注意此时class和style绑定不支持数组和对象,也就是说v-binid值为对象的时候,在里面绑定class和style,class和style的值是不能写数组和对象的。
基本使用方法总结:
<!-- 绑定一个属性 -->
<img v-bind:src="imageSrc">
<!-- 动态特性名 (2.6.0+) -->
<button v-bind:[key]="value"></button>
<!-- 缩写 -->
<img :src="imageSrc">
<!-- 动态特性名缩写 (2.6.0+) -->
<button :[key]="value"></button>
<!-- 内联字符串拼接 -->
<img :src="'/path/to/images/' + fileName">
绑定 class 或 style
用字符串拼接的形式去动态绑定class或style是很麻烦的,例如:
<div :class = "`${ isA && 'a'} ${..}`"></div>
由于字符串拼接麻烦且易错,所以在绑定class或style特性时,Vue做了增强,表达式的类型除了字符串之外,还可以是数组或对象。
绑定class
-
对象语法
<div v-bind:class="{ red: true }"></div> <!--对象里面表示使不使用某个类 如果是true就使用 页面中显示为<div class="red"></div>-->上面的语法表示red这个类存不存在,取决于它对应的值是不是true。
-
数组语法 我们可以把一个数组传给
v-bind:class来应用一个class列表,也就是说让div去动态绑定多个class。<div v-bind:class="[classA, classB]"></div>第一眼看上去好像没啥用啊,这样做和
class = "classA classB"是啥区别?区别还是有滴,不使用
v-bind的话就不能动态绑定多个class,可以给classA,classB变化不同的名字。不用数组是没办法实现这样的功能的,我自己尝试了一下,如果使用
:class = "classA classB",然后在data中分开声明classAclassB,这样整个控制区域的语句都会被注释成<!---->。 -
在数组语法中还可以使用三元表达式来切换class
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>但是属性值一多,这样看起来就很麻烦,还不如用对象的形式来写。
-
在数组语法中可以使用对象语法
这两种方式各有各的好处,使用数组的方式绑定可以在data中切换多种类名,
classA可以是任意的类名。而用对象的方式绑定可以根据情况判断是否加上这个类。<div v-bind:class="[classA, { classB: isB, classC: isC }]">所以说,切换类名的时候用数组的方式,动态添加或删除类名的时候用对象的方式。
-
v-bind:class可以与普通class共存
<div v-bind:class="classA" class="red">在vue编译dom语句的过程中,他发现这里要绑定class。这样他就会把符合条件的class放到原有的class里面去,所以他们并不冲突。
怎么证明他只是放进去嘞?
我们可以看看下面这个例子:isRed为false。如果最后渲染的HTML页面上还是有
class = "red",就说明他真的只是放进去而已啦。<div :class = "{red: isRed}" class = "red"></div>最后编译后的结果中有red。因为绑定的class只是表示如果isRed为true就添加到class中,如果是false就不添加,不会影响原来的class里面的元素。
绑定style
-
使用对象语法 看着比较像CSS,但其实是一个JavaScript对象。CSS属性名可以用驼峰式(camelCase)或者短横线分隔(kebab-case)来命名,但是使用短横线分隔时要用
""括起来。<div v-bind:style="{ fontSize: size + 'px' }"></div>data: { size: 30 }也可以直接绑定一个样式对象 这样模板会更清晰:
<div v-bind:style="styleObject"></div>data: { styleObject: { fontSize: '13px' } } -
使用数组语法 数组语法可以将多个样式对象应用到同一个元素
<div v-bind:style="[styleObjectA, styleObjectB]"></div> -
自动添加前缀 绑定style时 ,使用需要添加浏览器引擎前缀的CSS属性时(如 transform),Vue.js会自动侦测并添加相应的前缀。
-
多重值 从2.3.0起,你可以为style绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值:
<div v-bind:style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>这样写只会渲染数组中最后一个被浏览器支持的值,在本例中,如果浏览器支持不带前缀的flexbox,那么就只会渲染
display: flex,否则就会加上前缀-ms-flexbox。 -
我们使用了绑定的style和普通的style元素是可以共存的,他和class的道理是一样的。但是他有一点小区别,就是当我们原来设置了一个
color:white,然后在绑定的时候又设置了color:green,最终的效果肯定是绿色。原因是style样式是元素上本身就有的,而vue编译的时候会加上绑定的样式,那么vue加上的样式肯定是后来的,所以一定会覆盖前面相同属性的样式。
v-on指令
行间定义方法
v-on指令简写:@
v-on指令可以监听DOM事件,并在触发时运行一些JavaScript代码。事件类型由参数指定,参数就是我们平常使用的事件名字,例如click。(在指令中,参数就是指令的冒号后面的东西,比如v-on:click的参数是click)
<div id="app">
<button v-on:click="counter += 1">点击加 1</button>
<p>按钮被点击了 {{ counter }} 次</p>
</div>
const vm = new Vue({
el: 'app',
data: {
counter: 0
}
})
把事件写在行间有一个好处就是,在看html的时候,就知道非常轻易的知道要监听什么事件,要处理什么逻辑。不像之前要获取元素再为元素添加事件,我们在看html的时候根本不知道哪个元素被监听了事件,使用了什么逻辑,不利于阅读代码。
但是很多事件处理逻辑是非常复杂的,这时候直接把JavaScript代码写在v-on指令中是不可行的,要把处理函数放在js里面,所以v-on还可以接收一个需要调用的方法名称。
<div id="app">
<!-- `addCounter` 是在下面定义的方法名,和data中的属性一样可以直接写,不需要以"."的形式去调用 -->
<button v-on:click="addCounter">点击加 1</button>
<p>按钮被点击了 {{ counter }} 次</p>
</div>
js中定义方法和this指向
const vm = new Vue({
el: '#app',
//data是放数据的。如果在里面放方法,虽然可以执行,但是不够语义化。
//而且这个时候的this是window。
//在这里放方法的话还要通过vm.counter的方式来获取需要的数据,很麻烦。
data: {
counter: 0
},
//应该在methods对象中定义方法
methods: {
addCounter: function (e) {
// this 在方法里指向当前 Vue 实例
this.counter += 1;
// e 是原生 DOM 事件
cosnole.log(e.target);
}
}
})
methods中的函数,也和data一样会直接代理给Vue实例对象,所以可以直接运行:
vm.addCounter();
除了直接调用方法,也可以在内联JavaScript语句中使用this调用方法。
<div id="app">
<button v-on:click="addCounter(5)">点击加 5</button>
<p>按钮被点击了 {{ counter }} 次</p>
</div>
new Vue({
el: '#app',
data: {
counter: 0
},
methods: {
addCounter: function (num) {
this.counter += 5;
},
a(){
console.log("a");
//直接调用vm实例里面的addCounter
this.addCounter()
}
}
})
这也是要把data和method代理给vm实例对象的一个原因,如果不直接代理给实例对象,那我们在书写的过程中要使用vm.data.a这样的方式去调用属性,看起来很不舒服。
事件对象与参数传递
传递事件对象:
<button v-on:click = "addCounter">点击+1</button>
new Vue({
el: '#app',
data: {
counter: 0
},
methods: {
//传递事件对象
addCounter: function (e) {
console.log(e.target)
}
}
})
传递参数:
要注意的是:在行内的方法名后面写括号只表示传参数,而不是说立即执行。括号里面不写参数和直接不写括号是一样的,都是等到触发事件了才执行函数。
<button v-on:click = "addCounter(5)">点击+1</button>
methods: {
//传递参数
addCounter: function (num) {
console.log(num)
}
}
传参之后,事件对象的位置就被参数代替了。我们无法直接通过function(num, e)来获取到事件对象,这个时候我们要在行间传递事件对象给处理函数。在内联语句中使用事件对象时,可以利用特殊变量$event:
<div id="app">
<button v-on:click="addCounter(5, $event)">点击加 5</button>
<p>按钮被点击了 {{ counter }} 次</p>
</div>
new Vue({
el: '#app',
methods: {
addCounter: function (num, e) {
this.counter += 5;
cosnole.log(e.target);
}
}
})
一些不常用的方法
-
可以绑定动态事件,Vue版本需要2.6.0+。
<div v-on:[event]="handleClick">点击 弹出1</div>const vm = new Vue({ el: '#app', data: { //设置事件名字 event: 'click' }, methods: { //设置处理函数 handleClick () { alert(1); } } }) -
可以和v-bind一样,不带参数绑定一个对象,Vue版本需要2.4.0+。
-
使用方法:{ 事件名: 事件执行函数 }
-
使用此种方法不支持函数传参&修饰符,会报错。
<div v-on="{ mousedown: doThis, mouseup: doThat }"></div>
-
为什么要在HTML中监听事件?
- 扫一眼HTML模板便能轻松定位在JavaScript代码里对应的方法。
- 你无须在JavaScript里手动绑定事件,你的ViewModel(vm)代码可以是非常纯粹的逻辑,和DOM完全解耦,更易于单元测试。
- 当一个ViewModel被销毁时,所有的事件处理器都会自动被删除,不用担心如何清理它们。
列表渲染
利用v-for指令,基于数据多次渲染元素。
在v-for中使用数组
用法:(item, index) in items
items:源数据数组item:数组中每一项元素的别名,可以在控制区域中使用,包括但不限于子元素和行间。index:可选,索引。item和index都写了的时候要用括号括起来,虽然不括起来也能使用,但是括起来的可阅读性更高一些。
可以访问所有父作用域的属性:
<ul id="app">
<li v-for="(person, index) in persons">
<!--注意这里的父作用域是指vm,
所以我们可以访问所有data中的内容,不只是item中的内容-->
{{ parentMessage }}---{{ index }}---{{ person.name }}---{{ person.age }}
<!--输出4个li-->
</li>
</ul>
const vm = new Vue({
el: '#app',
data: {
parentMessage: 'Parent',
persons: [
{ name: '小饼1', age: 18 },
{ name: '小饼2', age: 20 },
{ name: '小饼3', age: 22 },
{ name: '小饼4', age: 88 },
]
}
})
在v-for中使用对象
用法:(value, key, index) in Object
- value:对象值
- key:可选,键名
- index:可选,索引
<ul id="app">
<li v-for="(value, key, index) in cake">
{{ value }}
<!--输出3个li 分别是: 饼 20 163cm-->
</li>
</ul>
const vm = new Vue({
el: '#app',
data: {
cake: {
name: '饼',
age: 20,
height: '163cm'
}
}
})
不管是数组还是对象,都可以利用of替代in作为分隔符,因为它更接近迭代器的语法(官方推荐),但是很少有人这么使用。
<div v-for="item of items"></div>
在v-for中使用数字
用法:n in num,n表示数字,从1开始。
<div>
<span v-for="n in num">{{ n }} </span>
<!--输出10个span 分别是1-10-->
</div>
const vm = new Vue({
el: '#app',
data: {
num: 10
}
})
在v-for中使用字符串
用法:str in string,str表示源数据字符串中的每一个字符。
<div>
<span v-for="str in string">{{ str }} </span>
<!--输出8个span 分别是x i a o b i n g -->
</div>
const vm = new Vue({
el: '#app',
data: {
string: 'xiaobing'
}
})
循环一段包含多个元素的内容
当我们需要重复循环一段包含多个元素的内容,不可能对每个元素都使用一次v-for。这时候就需要把它包裹起来去循环,这时候template就是最好的包裹元素。
循环渲染一段包含多个元素的内容:
<ul id="app">
<template v-for="person in persons">
<li>{{ item.msg }}</li>
<li>哈哈</li>
</template>
</ul>
const vm = new Vue({
el: '#app',
data: {
persons: ['shan', 'jc', 'cst', 'deng']
}
})
关于key
Vue更新使用v-for渲染的元素列表时,它默认使用"就地更新"的策略。如果数据项的顺序被改变,Vue将不会移动没有被绑定的DOM元素来匹配数据项的顺序,而是简单复用此处每个元素。举个例子吧:
在下面的demo中,我们通过点击修改数组排列顺序的方式,希望达成点击下移按钮,整个li就被移动到下面去的效果。
<ul id="app">
<li v-for="(person, index) in persons">
{{ person }}
<input type="text" />
<button @click="handleClick(index)">下移</button>
</li>
</ul>
const vm = new Vue({
el: '#app',
data: {
persons: ['杉杉', '思彤哥', '成哥', '邓哥']
},
methods: {
handleClick (index) {
//返回的结果是一个被删除的元素的数组
const deleteItem = this.persons.splice(index, 1);
this.persons.splice(index + 1, 0, ...deleteItem);
}
}
})
在"就地复用"策略中,点击按钮、输入框不随文本一起下移 。
实际上我们每次点击按钮,下移的都只是文本。原因是输入框没有与data数据绑定,所以vue默认使用已经渲染的dom,然而"杉杉"这些文本是与data数据绑定的,所以文本被重新渲染。这种处理方式在vue中是默认的列表渲染策略,因为高效 。
但是在更多的时候,我们并不需要这样去处理。
如果我们每次改动persons数组的一个顺序,vue就去帮我们重新渲染整个数组,显然是很耗费性能的,但是也没有其他办法。因为vue根本就没办法去区分列表中的每一项,这样就无法记录哪个元素的顺序变了,再针对变化的元素进行渲染。
所以为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,我们需要为每项提供一个唯一key特性。
其实就相当于给每项元素起一个名字,这样,当我们重新排列数组元素的时候,Vue会基于key的顺序的变化来排列和移动元素,而不会再复用了。并且会移除key不存在的元素。
key的使用方法
预期值:number | string 一个父元素的所有子元素都必须要有独特的key,重复的key会造成渲染错误。所以key值应该是唯一的
<ul id="app">
<li v-for="(person, index) in persons" :key="person">
{{ person }}
</li>
</ul>
const vm = new Vue({
el: '#app',
data: {
persons: ['杉杉', '思彤哥', '成哥', '邓哥']
}
})
不建议将数组的索引作为key值,如:
<li v-for="(person, index) in persons" :key="index">
{{ person }}
</li>
原因是:当改变数组时,页面会重新渲染。渲染之前Vue会根据key值对应的元素内容有没有变化来判断要不要移动元素 如果变化了就会重新生成元素
例如当页面重新渲染时,key值为"0"的元素为<li>小饼</li>,页面重新渲染, key值为"0"的元素也为<li>小饼</li>,那么为了节约性能Vue就会移动这个li元素,而不是重新生成一个元素再插入列表中。
在这个前提下,当使用数组的索引作为key值时,页面重新渲染后,元素的key值会重新被赋值。例如我们将数组进行反转:
反转前:
| 元素 | key值 | |
|---|---|---|
<li>杉杉</li> |
0 | |
<li>思彤哥</li> |
1 | |
<li>成哥</li> |
2 | |
<li>邓哥</li> |
3 |
反转后:
| 元素 | key值 | |
|---|---|---|
<li>邓哥</li> |
0 | |
<li>成哥</li> |
1 | |
<li>思彤哥</li> |
2 | |
<li>杉杉</li> |
3 |
Vue在渲染的时候,会比对渲染前后拥有同样key的元素,发现有变动,就会再生成一个元素。这样如果用索引作key值的话,那么此时,所有的元素都会被重新生成,性能消耗非常大。
那么如果我们用<li>里面的内容如"杉杉","思彤哥"等等作为key值,vue渲染之前一看,里面的内容没变,就直接移动元素了。消耗的性能相对小很多。
在其他地方使用key
key不仅为v-for所有,它可以强制替换元素,而不是重复使用它:
<ul id="app">
<button @click="show = !show">{{ show ? '显示' : '隐藏'}}</button>
<!--如果不使用key 那么每次切换的就只有button中的文字
对于input,vue发现更改之前和更改之后的元素是一样的,他就干脆不重新生成了-->
<!--当然如果给input加上不同的value也能解决重复使用的问题
反正只要这两个元素不是一模一样的就行-->
<input type="text" v-if="show" key="a" />
<input type="text" v-else key="b" />
</ul>
const vm = new Vue({
el: '#app',
data: {
show: true
}
})
不设置key也不行,数组下标也不给用,那平时开发的时候key要怎样才能做到唯一?
跟后台协作时,传回来的每一条数据都有一个id值,这个id就是唯一的,用id做key即可。
v-for 和 v-if 一同使用
不要把v-if和v-for同时用在同一个元素上。因为当vue处理指令的时候,v-for比v-if具有更高的优先级,就是说先执行for再执行if,然后v-if就会在每个v-for循环中重复运行。举个例子哈,看看下面这个代码:
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
将会经过如下运算(虽然人家源代码当然不是这么写的,但是我们可以这样感受感受内个意思):
//users是渲染的元素组成的数组
this.users.map(function (user) {
if (user.isActive) {
return user.name
}
})
这样一来,哪怕我们渲染出的活跃用户的元素只有一小部分,他也得在每次重新渲染的时候遍历整个列表,然后对每一个元素进行判断。他也不管活跃用户(我们渲染的数据)有没有发生变化,反正你要是这么写了他就每次渲染之前都遍历(拽)。
这样显然会比先判断有哪些数据需要被渲染,再去用v-for去渲染要慢很多。
所以以下两种场景,我们可以做出如下处理。
-
为了过滤一个列表中的项目的时候,可以利用计算属性。
如果v-for和v-if同时使用,代码会是这样的:
<ul id="app"> <!--把活跃用户的名字都打出来--> <li v-for="user in users" v-if="user.isActive" :key="user.id" > {{ user.name }} </li> </ul>const vm = new Vue({ el: '#app', data: { users: [ { name: 'shan', isActive: true, id: 1}, { name: 'jc', isActive: false, id: 2}, { name: 'cst', isActive: false, id: 3}, { name: 'deng', isActive: true, id: 4}, ] } })为了优化,我们可以把上面的代码更新为:
<!-- 通过原来的数组 得到一个符合渲染条件的新数组 渲染这个新的数组 --> <ul id="app"> <li v-for="user in activeUsers" :key="user.id"> {{ user.name }} </li> </ul>const vm = new Vue({ el: '#app', data: { users: [ { name: 'shan', isActive: true, id: 1}, { name: 'jc', isActive: false, id: 2}, { name: 'cst', isActive: false, id: 3}, { name: 'deng', isActive: true, id: 4}, ], }, computed:{ //用计算属性过滤users activeUsers(){ return this.users.filter(user => user.isActive); } } }) -
为了避免渲染本应该被隐藏的全部列表的时候
<ul> <!--如果我们这样写 即使所有的li都不应该被显示 他还是会在生成所有的li之后遍历li 拿出合适的li然后进行渲染--> <li v-for="user in users" v-if="shouldShowUsers" :key="user.id" > {{ user.name }} </li> </ul>const vm = new Vue({ el: '#app', data: { users: [ { name: 'shan', isActive: true, id: 1}, { name: 'jc', isActive: false, id: 2}, { name: 'cst', isActive: false, id: 3}, { name: 'deng', isActive: true, id: 4}, ], shouldShowUsers: false } })html部分可替换成为:
<ul v-if="shouldShowUsers"> <li v-for="user in users" :key="user.id" > {{ user.name }} </li> </ul>将v-if置于外层元素上,我们不会再对列表中的每个用户检查shouldShowUsers。我们只检查它一次,shouldShowUsers为否的时候不会运算 v-for。
自己提出的一个小问题:那为什么VUE不优化一下,在同一节点中,让if的优先级高于for呢?
自问自答:额..不先循环怎么得到要进行判断的元素?只有在循环拿到每个元素的时候,才能去判断这个元素符不符合条件吧..
(想通之后感觉这个问题好蠢,怪不得百度的时候都搜不到)
怎么就八千多字了,我还想讲讲v-for的源码嘞..那就放到下一篇吧