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">
相关推荐
uhakadotcom几秒前
简单易懂的Storybook介绍:让前端UI组件开发变得更高效
前端·javascript·面试
uhakadotcom4 分钟前
PyTorch 2.0:最全入门指南,轻松理解新特性和实用案例
后端·面试·github
bnnnnnnnn5 分钟前
前端实现多服务器文件 自动同步宝塔定时任务 + 同步工具 + 企业微信告警(实战详解)
前端·javascript·后端
返乡coder5 分钟前
一文掌握React基础用法:从零开始构建现代Web应用
前端
DataFunTalk5 分钟前
乐信集团副总经理周道钰亲述 :乐信“黎曼”异动归因系统的演进之路
前端·后端·算法
JiangJiang10 分钟前
🚀 Vue 人看 useMemo:别再滥用它做性能优化
前端·react.js·面试
DataFunTalk16 分钟前
开源一个MCP+数据库新玩法,网友直呼Text 2 SQL“有救了!”
前端·后端·算法
小万编程19 分钟前
基于SpringBoot+Vue的汽车展销平台【提供源码+论文1.5W字+答辩PPT+项目部署】
vue.js·spring boot·汽车
cg501720 分钟前
Spring Boot 使用 SMB 协议
java·前端·spring boot·smb
谁还不是一个打工人42 分钟前
vue2实现在屏幕中有一个小机器人可以随意移动
前端·javascript