Vue 内置指令
指令是 Vue 模板里以 v- 开头的特殊属性,告诉 Vue"这个元素怎么渲染、怎么响应数据变化"。常用的就那么八九个,记住它们你就能写出绝大多数页面。本章系统过一遍。
1. v-if / v-else-if / v-else:条件渲染
v-if 是"真条件渲染":表达式为 false 时,对应的 DOM 节点根本不存在。适合不常切换的场景。
<template>
<!-- v-if:真条件渲染,不满足时 DOM 完全不存在 -->
<p v-if="status === 'loading'">加载中...</p>
<p v-else-if="status === 'error'">出错了</p>
<p v-else>加载完成</p>
<!-- 配合 template 标签包裹多个元素(不会渲染出 template) -->
<template v-if="showPanel">
<h3>面板标题</h3>
<p>面板内容</p>
<button>关闭</button>
</template>
</template>
<script setup>
import { ref } from "vue";
const status = ref("loading");
const showPanel = ref(true);
</script>三个注意点:
v-else-if和v-else必须紧跟在v-if元素后面,中间不能有其他元素,否则 Vue 识别不到。- 需要条件渲染多个元素时,用
<template v-if>包裹(template不会渲染成真实 DOM)。 - 因为 v-if 是真的创建/销毁 DOM,切换有性能开销。频繁切换用 v-show。
2. v-show:切换 display
v-show 不管条件真假,DOM 都会被渲染,只是通过 display: none 控制显隐。
<template>
<!-- v-show:切换 display 样式,DOM 一直在 -->
<div v-show="isVisible">这段一直在 DOM 里,只是被隐藏</div>
<!-- v-if 不满足时,DOM 节点根本不存在 -->
<div v-if="isVisible">这段在隐藏时不存在</div>
</template>
<script setup>
import { ref } from "vue";
const isVisible = ref(false);
</script>
<!-- 切换频率高 → v-show(DOM 复用,无创建开销)
切换频率低 → v-if(节省初始渲染)
运行时条件变化少 → v-if(更轻量) -->v-if vs v-show 是面试常考题,记住区别:
- v-if:false 时 DOM 不存在;v-show:false 时 DOM 存在但隐藏。
- v-if 切换成本高(创建/销毁);v-show 初始成本高(全部渲染)。
- 频繁切换用 v-show(标签页、折叠面板);条件很少变化用 v-if(权限判断、首次加载)。
- v-show 不能用在
<template>上,因为它需要一个真实元素来设 display。
3. v-for:列表渲染(重点)
v-for 遍历数组、对象、数字范围,生成一组元素。它是 Vue 中最容易踩坑的指令之一。
<template>
<!-- v-for 遍历数组,必须给 :key(用唯一 id,不要用 index) -->
<li v-for="item in products" :key="item.id">
{{ item.name }} - ¥{{ item.price }}
</li>
<!-- 需要索引时 -->
<li v-for="(item, index) in products" :key="item.id">
{{ index + 1 }}. {{ item.name }}
</li>
<!-- 遍历对象 -->
<li v-for="(value, key) in user" :key="key">
{{ key }}: {{ value }}
</li>
<!-- 遍历数字范围 -->
<span v-for="n in 10" :key="n">{{ n }} </span>
<!-- 遍历对象数组,嵌套子组件 -->
<user-card v-for="u in users" :key="u.id" :data="u" />
</template>
<script setup>
import { ref } from "vue";
const products = ref([
{ id: 1, name: "苹果", price: 5 },
{ id: 2, name: "香蕉", price: 3 },
]);
const user = ref({ name: "小明", age: 20, city: "北京" });
const users = ref([{ id: 1 }, { id: 2 }]);
</script>几个要点:
- 必须给
:key:帮 Vue 复用 DOM,提高渲染效率。不写会有警告。 - 遍历对象时,顺序是
(value, key)或(value, key, index)。 - 遍历数字
v-for="n in 10",n 从 1 到 10(不是 0 到 9)。 - v-for 和 v-if 不要写在同一个元素上:Vue 3 中 v-if 优先级更高,会读不到 v-for 的循环变量。用
<template v-for>包一层再写 v-if。
4. :key 为什么这么重要?
key 是 Vue 复用 DOM 的"身份证"。看下面这个反面教材:
<template>
<!-- ❌ 反面教材:用 index 作 key -->
<li v-for="(item, i) in list" :key="i">{{ item.text }}</li>
<!-- 当 list 顶部插入新元素,index 全部错位,
Vue 会"误以为"原来的项变了,导致状态混乱、
输入框内容串行、动画错位等诡异 bug -->
<!-- ✅ 正确:用稳定的唯一 id -->
<li v-for="item in list" :key="item.id">{{ item.text }}</li>
</template>
<script setup>
const list = [
{ id: "a1", text: "第一项" },
{ id: "a2", text: "第二项" },
];
</script>假设列表是 [A, B, C],你头部插入新元素 X,变成 [X, A, B, C]。用 index 作 key 的话,Vue 会按位置匹配——它以为"位置 0 的元素从 A 变成了 X",于是全部元素被重新渲染,且如果元素内部有状态(比如输入框里打了字),状态会串行错位。用稳定 id 作 key,Vue 就能精确匹配,只创建一个新 DOM 节点。
所以记一条: 用稳定的唯一 id,不要用 index。如果列表纯粹是展示型(无状态、不会重排),用 index 也勉强可以;但养成用 id 的习惯永远不会错。
5. v-bind:属性绑定(缩写 :)
把数据绑定到元素的属性上。支持动态属性名、class、style 的特殊语法:
<template>
<!-- v-bind 缩写 :,绑定属性 -->
<img :src="avatar" :alt="name" />
<a :href="url">链接</a>
<!-- 动态属性名:[propName] -->
<a :[attrName]="url">链接</a>
<!-- class 绑定:对象/数组/三目 -->
<div :class="{ active: isActive, error: hasError }"></div>
<div :class="['btn', isActive ? 'btn-on' : 'btn-off']"></div>
<div :class="baseClass"></div>
<!-- style 绑定:对象/数组 -->
<div :style="{ color: 'red', fontSize: size + 'px' }"></div>
<div :style="[baseStyle, overrideStyle]"></div>
</template>
<script setup>
import { ref } from "vue");
const avatar = ref("/a.png");
const name = ref("小明");
const url = ref("/page");
const attrName = ref("href");
const isActive = ref(true);
const hasError = ref(false);
const size = ref(16);
</script>class 和 style 的对象/数组语法是 Vue 模板的精髓——让样式也"响应式"起来。:[attrName]="url" 这种动态参数语法是 Vue 3 才有的,适合写高复用的通用组件。
6. v-on:事件绑定(缩写 @)
v-on 监听 DOM 事件,缩写 @。支持链式修饰符:
<template>
<!-- v-on 缩写 @,事件绑定 -->
<button @click="onClick">点我</button>
<input @input="onInput" @focus="onFocus" />
<!-- 修饰符:链式 -->
<form @submit.prevent="onSubmit"> <!-- .prevent 阻止默认 -->
<button @click.stop="onSave"> <!-- .stop 阻止冒泡 -->
<a @click.once="onJump">链接</a> <!-- .once 只触发一次 -->
</button>
</form>
<!-- 按键修饰符 -->
<input @keyup.enter="onSearch" @keyup.esc="onCancel" />
<!-- v-model:表单双向绑定(后续专门一章) -->
<input v-model="keyword" />
<p>{{ keyword }}</p>
</template>修饰符让事件处理更声明式——你不用在回调里手动 e.preventDefault() 或 e.stopPropagation()。常用修饰符:
.stop:阻止事件冒泡(等价 stopPropagation)。.prevent:阻止默认行为(等价 preventDefault)。.capture:使用事件捕获模式。.self:仅当事件目标是元素本身时才触发。.once:只触发一次。.passive:提升滚动性能(移动端常用)。- 按键修饰符:
.enter.tab.delete.esc.space.up.down.left.right。 - 系统键修饰符:
.ctrl.alt.shift.meta(Mac 是 Command)。 - 鼠标修饰符:
.left.right.middle。
7. v-model:表单双向绑定
v-model 是 :value + @input 的语法糖,让表单元素和数据双向同步。它在《表单与 v-model》一章详讲。
8. v-html 与 v-text
这两个指令分别用于渲染原始 HTML 和文本:
v-html="rawHtml":把字符串当 HTML 渲染(有 XSS 风险,只在数据可信时用)。v-text="msg":等价于双花括号{{ msg }},很少用,优先用双花括号。
9. v-once 与 v-memo:性能优化
v-once:只渲染一次,之后数据变化也不更新。适合纯静态内容(版权信息、说明文字)。v-memo="[a, b]":只有当 a 或 b 变化时才重新渲染子树。在大型 v-for 列表中优化性能用。
这两个是高级特性,新手用不上,知道有就行。
10. 自定义指令
除了内置指令,Vue 还允许你自己造指令。比如一个 v-focus(元素挂载时自动聚焦):
<script setup>
import { ref, onMounted } from "vue";
// 自定义指令:在 <script setup> 里以 v 开头的小驼峰变量名注册
const vFocus = {
mounted: (el) => el.focus(),
};
const vHighlight = {
mounted(el, binding) {
el.style.background = binding.value || "yellow";
},
};
</script>
<template>
<input v-focus />
<p v-highlight="'lightblue'">这段高亮</p>
</template>实际项目里,自定义指令用得不多——大部分需求用组件就能解决。但偶尔遇到"操作 DOM"的需求(聚焦、拖拽、tooltip),指令是更优雅的封装方式。
小结
这一章你系统过了一遍 Vue 的所有内置指令。重点记:v-if vs v-show 的区别、v-for 必须配 key(用 id 不用 index)、修饰符的链式写法。下一篇我们把事件处理单独展开讲——它是前端交互的核心。
← 上一篇 Vue 计算属性与侦听器
下一篇 Vue 事件处理 →