Vue 3 开发必备:模板语法、指令详解及常见面试题避坑指南

Vue 的模板语法、指令和插值是构建 Vue 应用的核心,它们为开发者提供了便捷的方式来实现数据与视图的绑定,以及丰富多样的交互效果。

一、插值:数据绑定的基础

1. 什么是插值?

插值( Interpolation )是 Vue 模板语法中用于将数据动态嵌入到 HTML 结构中的一种方式。

它允许开发者在模板中直接引用 Vue 实例中的数据,并实时展示其值。

简单来说,插值就像是在 HTML 页面里预留一个 "占位符",Vue 会用实际的数据替换这个占位符,从而实现数据与视图的绑定。

2. 文本插值 {{ }}

文本插值使用双大括号 {{ }} 来嵌入数据,它不仅能显示简单的字符串,还支持简单的 JavaScript 表达式运算。

html 复制代码
<template>
  <div>
    <!-- 显示 message 变量的值,最终会显示:Hello, Vue! -->
    <p>{{ message }}</p>
    <!-- 计算表达式 2 + 3 的值并显示,结果为:5 -->
    <p>{{ 2 + 3 }}</p>
    <!-- 根据 isTrue 的值,条件为真显示 Yes,否则显示 No,当前显示:Yes -->
    <p>{{ isTrue? 'Yes' : 'No' }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue';

// 定义响应式变量 message,值为 'Hello, Vue!'
const message = ref('Hello, Vue!');
// 定义响应式变量 isTrue,值为 true
const isTrue = ref(true);
</script>

不过,在双大括号中只能使用简单的表达式,不能使用语句,如 if 语句、for 循环等。这是因为插值表达式的目的是简洁地计算并显示数据,而不是执行复杂的逻辑。

自动转义机制:Vue 会自动对插值内容进行转义,以防止 XSS 攻击。

XSS 攻击,即跨站脚本攻击(Cross-Site Scripting),是一种常见的 web 安全漏洞。攻击者通过注入恶意的脚本代码(如 JavaScript)到网页中,当用户访问该网页时,这些恶意脚本就会在用户的浏览器中执行,从而可能窃取用户的敏感信息(如 cookie、登录凭证等)、篡改页面内容或进行其他恶意操作。

例如,当我们插入包含 HTML 标签的字符串时,这些标签会被转义为 HTML 实体。

html 复制代码
<template>  
  <div>  
    <!-- 插值内容会自动转义 -->  
    <p>{{ '<div>Hello</div>' }}</p>  
  </div>  
</template>  

上述代码会输出 &lt;div&gt;Hello&lt;/div&gt;,而不是渲染成一个 div 元素。

  • 这与 v-html 指令形成鲜明对比,v-html 会直接渲染 HTML 代码,使用时需要格外注意安全问题。

3. 原始 HTML 插值 v-html

当我们需要将数据以 HTML 的形式渲染到页面上时,可以使用 v-html 指令。

html 复制代码
    <template>  
      <div>  
        <!-- 使用 v-html 渲染 HTML 内容 -->  
        <div v-html="rawHtml"></div>  
      </div>  
    </template>  

    <script>  
    export default {  
      data() {  
        return {  
          rawHtml: '<span style="color: red">This is red text</span>'  
        };  
      }  
    };  
    </script>  

⚠️ v-html 存在安全风险,它会直接渲染传入的 HTML 代码。如果这些代码来自用户输入或者不可信的来源,可能会导致XSS攻击。

二、指令:动态交互的核心

1. 什么是指令

指令(Directives)是一种特殊的 HTML 属性( Attribute ),用于为 HTML 元素添加特殊的逻辑或行为。它以 v- 为前缀(部分有简写形式)。

普通 HTML 属性(如 classidhref 等)主要用于定义元素的静态特性,其值通常是静态的,直接由浏览器进行渲染。

与普通的 HTML 属性不同,指令本质上是 Vue 对 DOM 操作的封装,通过元素标签上声明指令,开发者无需直接操作 DOM,而是通过声明式语法控制元素的渲染方式、响应事件、绑定数据等,从而实现数据驱动视图的动态更新。

2. 属性绑定 v-bind

动态绑定 class/style :v-bind 指令用于将 Vue 实例中的数据绑定到 HTML 元素的属性上。常见的应用场景包括动态绑定 classstyle 属性。

v-bind 可以简写为 :,这是一种更简洁的写法。

html 复制代码
<template>  
  <div>  
    <!-- 动态绑定 class 属性 -->  
    <p :class="{ active: isActive, 'text-bold': isBold }">This is a paragraph</p>  
    <!-- 动态绑定 style 属性 -->  
    <p :style="{ color: textColor, fontSize: fontSize + 'px' }">This is another paragraph</p>  
  </div>  
</template>  

<script>  
export default {  
  data() {  
    return {  
      isActive: true,  
      isBold: false,  
      textColor: 'blue',  
      fontSize: 16  
    };  
  }  
};  
</script>  

上述代码中,class 属性使用对象语法动态切换类名,style 属性使用对象语法动态设置样式。

3. 条件渲染

v-if vs v-show

v-ifv-show 都用于根据条件显示或隐藏元素,但它们的实现方式和性能特点有所不同。

指令 实现方式 性能特点 适用场景
v-if 惰性渲染:条件为假时不渲染或移除 DOM 元素,为真时插入 DOM 条件少变时性能优,频繁操作 DOM 开销大 条件不常变场景
v-show 始终渲染,通过切换元素的 display 属性控制显隐 频繁切换性能优,因为不涉及 DOM 增删 条件频繁变场景
html 复制代码
<template>  
  <div>  
    <!-- 使用 v-if 进行条件渲染 -->  
    <p v-if="score >= 90">优秀</p>  
    <p v-else-if="score >= 60">及格</p>  
    <p v-else>不及格</p>  
    <!-- 使用 v-show 进行条件渲染 -->  
    <p v-show="isVisible">This is visible</p>  
  </div>  
</template>  

<script>  
export default {  
  data() {  
    return {  
      score: 80,  
      isVisible: true  
    };  
  }  
};  
</script>  

v-else-ifv-else 的使用

v-else-ifv-elsev-if 的补充指令,用于实现多条件判断。它们必须紧跟在同级的 v-ifv-else-if 之后,且所有分支必须共享同一个父元素,否则无效。

<template> 上的 v-if

有时候,我们可能需要在不止一个元素上进行条件渲染。如果直接在多个元素上分别使用 v-if 会显得很繁琐,也会影响代码的可读性和维护性。这时,我们可以使用 <template> 标签来包裹这些元素,并在 <template> 上使用 v-if 指令。

<template> 标签是一个虚拟元素,它不会被渲染到 DOM 中,只是作为一个容器来组织多个元素。

html 复制代码
<template>
  <div>
    <template v-if="showGroup">
      <p>这是第一行文本</p>
      <p>这是第二行文本</p>
      <p>这是第三行文本</p>
    </template>
    <p v-else>条件不满足,不显示上面的文本组</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      showGroup: false
    };
  }
};
</script>
  • showGrouptrue 时,<template> 内的三个 <p> 元素会被渲染到 DOM 中。
  • showGroupfalse 时,只会渲染 v-else 对应的 <p> 元素。

注意: v-show 指令不能用在 <template> 标签上,它只能应用于具体的 HTML 元素上。

  • 因为 v-show 是通过修改元素的 display 属性来控制显隐的,而 <template> 本身不会被渲染到 DOM 中,没有 display 属性可供修改。

4. 列表渲染 v-for

遍历数组 / 对象 / 数字

v-for 指令用于循环渲染一组数据,它可以遍历数组、对象和数字。

html 复制代码
<template>  
  <div>  
    <!-- 遍历数组 -->  
    <ul>  
      <li v-for="(item, index) in items" :key="item.id">{{ index + 1 }}. {{ item.name }}</li>  
    </ul>  
    <!-- 遍历对象 -->  
    <ul>  
      <li v-for="(value, key, index) in user" :key="key">{{ index + 1 }}. {{ key }}: {{ value }}</li>  
    </ul>  
    <!-- 遍历数字 -->  
    <ul>  
      <li v-for="n in 5" :key="n">{{ n }}</li>  
    </ul>  
  </div>  
</template>  

<script>  
export default {  
  data() {  
    return {  
      items: [  
        { id: 1, name: 'Item 1' },  
        { id: 2, name: 'Item 2' },  
        { id: 3, name: 'Item 3' }  
      ],  
      user: {  
        name: 'John',  
        age: 30,  
        gender: 'male'  
      }  
    };  
  }  
};  
</script>  

key 的重要性

key 属性是 v-for 指令中极为关键 的一部分,它用于帮助 Vue 识别每个节点的唯一身份,进而优化虚拟 DOM(Virtual DOM)的更新效率。

  • Vue 的虚拟 DOM 采用 Diff 算法来比较新旧节点的差异,当数据发生变化时,通过 key 可以更高效地识别哪些节点需要更新,哪些节点可以直接复用,从而减少不必要的 DOM 操作,提高渲染性能。

因此,在使用 v-for 时,一定要为每个元素提供一个唯一的 key,通常使用数据的唯一标识符作为 key,如 id

为什么不建议用 index 作为 key ?

1. 数据顺序变化时导致渲染异常

当列表数据的顺序发生变化时( 比如进行插入、删除等操作 ),由于 index 是根据元素在数组中的位置动态生成的,一旦数组顺序改变,元素对应的 index 也会改变。这会使得 Vue 认为是新的元素插入或旧的元素删除,从而导致不必要的 DOM 操作,甚至可能出现渲染异常。

javascript 复制代码
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];

使用 index 作为 key 进行渲染:

html 复制代码
<li v-for="(user, index) in users" :key="index">{{ user.name }}</li>

如果在列表开头插入一个新用户:

js 复制代码
users.value.unshift({ id: 4, name: 'David' });

此时,原本 index 为 0 的 Alice 变成了 index 为 1,Bob 变成了 index 为 2,Charlie 变成了 index 为 3。即使有些元素的内容并没有改变, Vue 也会认为原来的元素都发生了变化,从而重新渲染整个列表。同理,当更新 Bob 的信息时:

js 复制代码
users.value[1].name = 'Bobby';

由于使用 index 作为 key,Vue 可能无法直接定位到 Bob 对应的节点进行更新,而是重新比较整个列表,导致性能下降。

2. 列表项存在状态绑定问题

列表项包含表单、动画等状态时,index 变化会导致状态跟随错误的节点。因为当数据顺序改变时,index 也会改变,这会使得原本绑定在某个元素上的状态被错误地应用到其他元素上。

html 复制代码
<li v-for="(user, index) in users" :key="index">
  {{ user.name }}
  <input v-model="user.inputValue"> <!-- 排序后输入值可能错位 -->
</li>

为什么不建议同时使用 v-ifv-for

在 Vue 3 里,v-if 优先级高于 v-for。模板编译时 v-if 先判断,这就使得 v-if 条件无法访问 v-for 作用域内定义的变量别名,若强行使用会引发错误。

而且,即便不考虑变量访问问题,二者同时使用时,会先构建完整的虚拟 DOM 结构,再用 v-if 过滤部分节点,这会带来额外的性能开销,数据量越大,性能问题越明显。

html 复制代码
<template>
<!-- 这里 v-if 尝试访问未通过 `v-for` 创建的 `item` 变量,会引发错误 -->
  <ul>
    <li v-for="item in items" v-if="item.isVisible" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue';
const items = ref([{ id: 1, isVisible: true }]);
</script>    
方法一:使用计算属性

可借助计算属性先对数据进行过滤,再用 v-for 遍历过滤后的数据。这样就能避免 v-ifv-for 同时出现在同一元素上。

html 复制代码
<template>
  <div>
    <!-- 遍历过滤后的数组 -->
    <ul>
      <li v-for="(item, index) in filteredItems" :key="item.id">{{ index + 1 }}. {{ item.name }}</li>
    </ul>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';

const items = ref([
  { id: 1, name: 'Item 1', isVisible: true },
  { id: 2, name: 'Item 2', isVisible: false },
  { id: 3, name: 'Item 3', isVisible: true }
]);

const filteredItems = computed(() => {
  return items.value.filter(item => item.isVisible);
});
</script> 
方法二:使用 <template> 标签

v-if 放到 <template> 标签上,让 v-ifv-for 外层进行判断,以此避免不必要的遍历。

html 复制代码
<template>
  <div>
    <!-- 外层 template 使用 v-for 遍历数字数组 -->
    <template v-for="number in numbers" :key="number">
      <!-- 内层 li 使用 v-if 判断是否为奇数 -->
      <li v-if="number % 2 === 1">{{ number }}</li>
    </template>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const numbers = ref([1, 2, 3, 4, 5, 6, 7, 8, 9]);
</script>

5. 事件绑定 v-on

v-on 指令用于在 DOM 元素上绑定事件监听器。通过 v-on,开发者能够监听如点击、键盘输入、鼠标移动等各类 DOM 事件,并在事件触发时执行对应的 JavaScript 方法。

v-on可以简写为 @,例如 v-on:click 可以简写为 @click

基本用法

html 复制代码
<template>
  <div>
    <!-- 绑定点击事件 -->
    <button @click="handleClick">点击我</button>
    <!-- 绑定键盘事件(监听回车键按下) -->
    <input type="text" @keyup.enter="handleEnter">
    <!-- 绑定鼠标移入移出事件 -->
    <div @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
      鼠标移到这里试试
    </div>
  </div>
</template>

<script setup>
  const handleClick = () => {
    console.log('按钮被点击了');
  };

  const handleEnter = () => {
    console.log('回车键被按下');
  };

  const handleMouseEnter = () => {
    console.log('鼠标移入');
  };

  const handleMouseLeave = () => {
    console.log('鼠标移出');
  };
</script>

传递参数

  1. 默认传参

在使用 v-on 绑定事件时,如果没有显式地传递参数,事件处理函数会默认接收到原生事件对象 $event

借助这个对象,你可以访问到与该事件相关的各种信息,例如事件类型、触发事件的 DOM 元素等。

html 复制代码
<template>
  <!-- 绑定点击事件,不传递参数,默认传递原生事件对象 -->
  <button @click="handleDefaultClick">默认传参点击</button>
</template>

<script setup>
// 定义事件处理函数,接收默认的原生事件对象
const handleDefaultClick = (event) => {
  // 打印事件类型,例如 'click'
  console.log('事件类型:', event.type); 
  // 打印触发事件的 DOM 元素
  console.log('触发事件的 DOM 元素:', event.target); 
};
</script>
  1. 传递自定义参数

    在事件处理方法中,可以通过 $event 访问原生事件对象,同时也能传递自定义参数:

html 复制代码
<template>
  <!-- 绑定点击事件,传递自定义参数和原生事件对象 -->
  <button @click="handleClick('自定义参数', $event)">点击传参</button>
</template>

<script setup>
// 定义事件处理函数
const handleClick = (arg, event) => {
  // 打印自定义参数。控制台输出:自定义参数。
  console.log(arg); 
  // 打印触发事件的 DOM 元素。控制台输出按钮对应的 DOM 元素对象信息。

  console.log(event.target); 
};
</script>    
  1. 内联语句传参

    也可以直接在 v-on 中使用内联 JavaScript 表达式:

html 复制代码
<template>
  <!-- 绑定点击事件,使用内联 JavaScript 表达式使 count 自增 -->
  <button @click="count += 1">点击自增(内联)</button>
  <!-- 显示当前 count 的值 -->
  <p>当前计数: {{ count }}</p>
</template>

<script setup>
import { ref } from 'vue';

// 使用 ref 创建响应式数据
const count = ref(0);
</script>  

事件修饰符

Vue 提供了一系列事件修饰符,用于简化常见的事件处理逻辑:

修饰符 作用 示例
.stop 阻止事件冒泡(等价于 event.stopPropagation() <button @click.stop="handleClick">
.prevent 阻止默认事件(如表单提交、链接跳转) <a @click.prevent="handleClick">
.capture 使用捕获模式触发事件 <div @click.capture="handleClick">
.self 只有事件源自当前元素时才触发 <div @click.self="handleClick">
.once 事件仅触发一次 <button @click.once="handleClick">
.passive 提升滚动性能(用于 scroll 事件) <div @scroll.passive="handleScroll">

按键修饰符

针对键盘事件,Vue 提供了按键修饰符,用于监听特定按键:

修饰符 对应按键 示例
.enter 回车键 <input @keyup.enter="handleEnter">
.tab Tab 键 <input @keydown.tab="handleTab">
.delete Delete 或 Backspace <input @keyup.delete="handleDelete">
.esc Esc 键 <input @keyup.esc="handleEsc">
.space 空格键 <input @keyup.space="handleSpace">
.up 向上箭头 <input @keyup.up="handleUp">
.down 向下箭头 <input @keyup.down="handleDown">

系统修饰键

结合 CtrlAltShiftMeta(Windows/Command 键)等修饰键使用:

修饰符 对应键 组合示例 作用
.ctrl Ctrl <input @keyup.ctrl.enter="submitForm"> 监听 Ctrl + Enter 组合键
.alt AltOption (⌥) <button @click.alt="openMenu"> 监听 Alt 键按下并点击
.shift Shift <input @keydown.shift="enableCaps"> 监听 Shift 键按下(如大写锁定)
.meta Win (⊞)或Command (⌘) <input @keyup.meta.c="copy"> 监听 Win + C 组合键(系统快捷键)

6. 表单输入绑定 v-model

v-model 是 Vue 提供的语法糖,用于在表单元素(如输入框)和组件响应式数据间创建双向绑定,让数据在组件与表单间双向同步。当用户输入文本或切换选项时,数据会自动更新;数据变化时,也能立刻反映到表单展示上。

v-model 本质上是 :value 绑定属性和 @input 事件的组合。

html 复制代码
<!-- 手动绑定 :value 和 @input -->
 <input 
    :value="inputText"
    @input="(e) => inputText = e.target.value">

<!-- 使用 v-model 指令 -->
<input v-model="inputText">

如何使用 v-model

  1. 基础用法 - 文本输入框
html 复制代码
<input v-model="message" type="text">
<p>输入内容: {{ message }}</p>

说明:v-model 绑定到 message 响应式变量,用户输入时 message 自动更新,反之修改 message 也会更新输入框内容。

  1. 单选框绑定
html 复制代码
<input type="radio" id="option1" value="option1" v-model="selectedOption">
<label for="option1">选项 1</label>
<input type="radio" id="option2" value="option2" v-model="selectedOption">
<label for="option2">选项 2</label>
<p>选中值: {{ selectedOption }}</p>

说明:多个单选框绑定同一 v-model,选中时 selectedOption 更新为对应 value

  1. 复选框绑定
  • 单个复选框(布尔值)
html 复制代码
<input type="checkbox" v-model="isChecked">
<p>是否选中: {{ isChecked }}</p>

isChecked 为布尔值,勾选时为 true,否则为 false

  • 多个复选框(数组)
html 复制代码
<input type="checkbox" value="apple" v-model="selectedFruits">苹果
<input type="checkbox" value="banana" v-model="selectedFruits">香蕉
<input type="checkbox" value="cherry" v-model="selectedFruits">樱桃
<p>选中水果: {{ selectedFruits }}</p>
js 复制代码
const selectedFruits = ref([])

selectedFruits 为数组,勾选的 value 会添加到数组中,取消则移除。

  1. 下拉框绑定
html 复制代码
<select v-model="selectedColor">
  <option value="red">红色</option>
  <option value="green">绿色</option>
  <option value="blue">蓝色</option>
</select>
<p>选中颜色: {{ selectedColor }}</p>

说明:v-model 绑定的 selectedColor 会与选中的 optionvalue 同步。

修饰符

v-model 提供了多个修饰符,用于处理常见的输入场景:

修饰符 作用 示例
.trim 自动过滤输入首尾空格 <input v-model.trim="message">
.number 将输入值转为数字(类型转换失败则保留原值) <input v-model.number="age">
.lazy 延迟更新(监听 change 事件,而不是默认的 input 事件) <input v-model.lazy="content">
相关推荐
Boilermaker199243 分钟前
【Java EE】SpringIoC
前端·数据库·spring
中微子1 小时前
JavaScript 防抖与节流:从原理到实践的完整指南
前端·javascript
天天向上10241 小时前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
趣多多代言人1 小时前
从零开始手写嵌入式实时操作系统
开发语言·arm开发·单片机·嵌入式硬件·面试·职场和发展·嵌入式
芬兰y1 小时前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁2 小时前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry2 小时前
Fetch 笔记
前端·javascript
拾光拾趣录2 小时前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟2 小时前
vue3,你看setup设计详解,也是个人才
前端
Lefan2 小时前
一文了解什么是Dart
前端·flutter·dart