Vue.js 的指令系统是其最强大的特性之一,通过以
v-
开头的特殊属性,我们可以在模板中声明式地绑定底层Vue实例的数据。本文将深入讲解Vue中最重要的指令,帮助掌握Vue的核心功能。
文章目录
-
- [1. v-model:双向数据绑定的核心](#1. v-model:双向数据绑定的核心)
- [2. v-bind:属性绑定的万能钥匙](#2. v-bind:属性绑定的万能钥匙)
- [3. v-if / v-else-if / v-else:条件渲染](#3. v-if / v-else-if / v-else:条件渲染)
-
- 基本用法
- [v-if vs v-show](#v-if vs v-show)
- [4. v-for:列表渲染](#4. v-for:列表渲染)
- [5. v-on:事件处理](#5. v-on:事件处理)
- [6. 其他重要指令](#6. 其他重要指令)
- [7. 自定义指令](#7. 自定义指令)
- [8. 实践](#8. 实践)
- [Vue.js 指令快速参考表](#Vue.js 指令快速参考表)

1. v-model:双向数据绑定的核心
基本用法
v-model
是Vue中实现双向数据绑定的指令,主要用于表单元素。
vue
<template>
<div>
<!-- 文本输入 -->
<input v-model="message" placeholder="输入消息">
<p>消息是: {{ message }}</p>
<!-- 多行文本 -->
<textarea v-model="text" placeholder="多行文本"></textarea>
<!-- 复选框 -->
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
<!-- 单选按钮 -->
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<!-- 选择框 -->
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
message: '',
text: '',
checked: false,
picked: '',
selected: ''
}
}
}
</script>
修饰符
v-model 提供了三个有用的修饰符:
vue
<!-- .lazy - 在 change 事件而非 input 事件触发时更新 -->
<input v-model.lazy="msg">
<!-- .number - 自动将用户输入转换为数值类型 -->
<input v-model.number="age" type="number">
<!-- .trim - 自动过滤用户输入的首尾空白字符 -->
<input v-model.trim="msg">
自定义组件中的 v-model
vue
<!-- 父组件 -->
<custom-input v-model="searchText"></custom-input>
<!-- 子组件 CustomInput.vue -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
</template>
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue']
}
</script>
2. v-bind:属性绑定的万能钥匙
基本用法
v-bind
用于动态绑定一个或多个属性,或组件 prop 到表达式。
vue
<template>
<div>
<!-- 绑定属性 -->
<img v-bind:src="imageSrc" v-bind:alt="imageAlt">
<!-- 缩写语法 -->
<img :src="imageSrc" :alt="imageAlt">
<!-- 绑定类名 -->
<div :class="{ active: isActive, 'text-danger': hasError }"></div>
<div :class="[activeClass, errorClass]"></div>
<div :class="classObject"></div>
<!-- 绑定样式 -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<div :style="[baseStyles, overridingStyles]"></div>
<div :style="styleObject"></div>
</div>
</template>
<script>
export default {
data() {
return {
imageSrc: 'https://example.com/image.jpg',
imageAlt: '示例图片',
isActive: true,
hasError: false,
activeClass: 'active',
errorClass: 'text-danger',
classObject: {
active: true,
'text-danger': false
},
activeColor: 'red',
fontSize: 30,
styleObject: {
color: 'red',
fontSize: '13px'
},
baseStyles: { color: 'blue' },
overridingStyles: { fontSize: '20px' }
}
}
}
</script>
动态属性名
vue
<template>
<!-- 动态属性名 -->
<a :[attributeName]="url">链接</a>
<!-- 当 attributeName 为 "href" 时,等价于 -->
<a :href="url">链接</a>
</template>
<script>
export default {
data() {
return {
attributeName: 'href',
url: 'https://www.example.com'
}
}
}
</script>
3. v-if / v-else-if / v-else:条件渲染
基本用法
这些指令用于条件性地渲染元素。
vue
<template>
<div>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
<!-- 多条件判断 -->
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else-if="type === 'C'">C</div>
<div v-else>Not A/B/C</div>
<!-- 使用 template 包装多个元素 -->
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
</div>
</template>
<script>
export default {
data() {
return {
awesome: true,
type: 'A',
loginType: 'username'
}
}
}
</script>
v-if vs v-show
vue
<template>
<div>
<!-- v-if 是"真正"的条件渲染,会销毁和重建元素 -->
<p v-if="showIf">通过 v-if 显示</p>
<!-- v-show 只是简单地切换元素的 CSS display 属性 -->
<p v-show="showShow">通过 v-show 显示</p>
</div>
</template>
<script>
export default {
data() {
return {
showIf: true,
showShow: true
}
}
}
</script>
使用建议:
- 如果需要非常频繁地切换,则使用
v-show
- 如果在运行时条件很少改变,则使用
v-if
4. v-for:列表渲染
基本用法
v-for
指令用于基于源数据多次渲染元素或模板块。
vue
<template>
<div>
<!-- 遍历数组 -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.message }}
</li>
</ul>
<!-- 带索引的遍历 -->
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }} - {{ item.message }}
</li>
</ul>
<!-- 遍历对象 -->
<ul>
<li v-for="value in object" :key="value">
{{ value }}
</li>
</ul>
<!-- 遍历对象,包含键名 -->
<ul>
<li v-for="(value, name) in object" :key="name">
{{ name }}: {{ value }}
</li>
</ul>
<!-- 遍历对象,包含键名和索引 -->
<ul>
<li v-for="(value, name, index) in object" :key="name">
{{ index }}. {{ name }}: {{ value }}
</li>
</ul>
<!-- 遍历数字 -->
<span v-for="n in 10" :key="n">{{ n }}</span>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{ id: 1, message: 'Foo' },
{ id: 2, message: 'Bar' }
],
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
}
}
</script>
维护状态(key的重要性)
vue
<template>
<div>
<!-- 不推荐:没有key -->
<div v-for="item in items">
{{ item.name }}
</div>
<!-- 推荐:使用唯一的key -->
<div v-for="item in items" :key="item.id">
{{ item.name }}
</div>
<!-- 数组变更方法 -->
<button @click="addItem">添加项目</button>
<button @click="removeItem">删除项目</button>
<button @click="updateItem">更新项目</button>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{ id: 1, name: '项目1' },
{ id: 2, name: '项目2' }
]
}
},
methods: {
addItem() {
this.items.push({
id: Date.now(),
name: `项目${this.items.length + 1}`
})
},
removeItem() {
this.items.pop()
},
updateItem() {
if (this.items.length > 0) {
this.items[0].name = '更新的项目1'
}
}
}
}
</script>
5. v-on:事件处理
基本用法
v-on
指令用于监听DOM事件,并在触发时执行JavaScript代码。
vue
<template>
<div>
<!-- 基本用法 -->
<button v-on:click="counter += 1">Add 1</button>
<p>按钮被点击了 {{ counter }} 次</p>
<!-- 缩写语法 -->
<button @click="greet">Greet</button>
<!-- 内联处理器中的方法 -->
<button @click="say('hi')">Say hi</button>
<button @click="say('what')">Say what</button>
<!-- 访问原始的DOM事件 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
<!-- 多个事件处理器 -->
<button @click="one($event), two($event)">
Submit
</button>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
name: 'Vue.js'
}
},
methods: {
greet(event) {
alert('Hello ' + this.name + '!')
if (event) {
alert(event.target.tagName)
}
},
say(message) {
alert(message)
},
warn(message, event) {
if (event) {
event.preventDefault()
}
alert(message)
},
one(event) {
console.log('第一个处理器')
},
two(event) {
console.log('第二个处理器')
}
}
}
</script>
事件修饰符
Vue为 v-on
提供了事件修饰符来处理DOM事件细节。
vue
<template>
<div>
<!-- 阻止单击事件继续传播 -->
<a @click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a @click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form @submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<div @click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<div @click.self="doThat">...</div>
<!-- 点击事件将只会触发一次 -->
<a @click.once="doThis"></a>
<!-- 滚动事件的默认行为将会立即触发,而不会等待onScroll完成 -->
<div @scroll.passive="onScroll">...</div>
</div>
</template>
按键修饰符
vue
<template>
<div>
<!-- 只有在 key 是 Enter 时调用 vm.submit() -->
<input @keyup.enter="submit">
<!-- 其他按键修饰符 -->
<input @keyup.tab="tabHandler">
<input @keyup.delete="deleteHandler">
<input @keyup.esc="escHandler">
<input @keyup.space="spaceHandler">
<input @keyup.up="upHandler">
<input @keyup.down="downHandler">
<input @keyup.left="leftHandler">
<input @keyup.right="rightHandler">
<!-- 系统修饰键 -->
<input @keyup.ctrl="ctrlHandler">
<input @keyup.alt="altHandler">
<input @keyup.shift="shiftHandler">
<input @keyup.meta="metaHandler">
<!-- 组合使用 -->
<input @keyup.ctrl.enter="ctrlEnterHandler">
<!-- 鼠标按钮修饰符 -->
<button @click.left="leftClick">左键</button>
<button @click.right="rightClick">右键</button>
<button @click.middle="middleClick">中键</button>
</div>
</template>
6. 其他重要指令
v-text 和 v-html
vue
<template>
<div>
<!-- v-text:更新元素的textContent -->
<span v-text="msg"></span>
<!-- 等价于 -->
<span>{{ msg }}</span>
<!-- v-html:更新元素的innerHTML -->
<p v-html="html"></p>
</div>
</template>
<script>
export default {
data() {
return {
msg: 'Hello World',
html: '<strong>粗体文本</strong>'
}
}
}
</script>
v-show
vue
<template>
<div>
<!-- 根据表达式的真假值,切换元素的display CSS属性 -->
<h1 v-show="ok">Hello!</h1>
</div>
</template>
<script>
export default {
data() {
return {
ok: true
}
}
}
</script>
v-pre
vue
<template>
<div>
<!-- 跳过这个元素和它的子元素的编译过程 -->
<span v-pre>{{ this will not be compiled }}</span>
</div>
</template>
v-once
vue
<template>
<div>
<!-- 只渲染元素和组件一次 -->
<h1 v-once>{{ title }}</h1>
<!-- 这也适用于子组件 -->
<my-component v-once :comment="msg"></my-component>
<!-- 带有v-for的v-once -->
<ul>
<li v-for="i in list" v-once :key="i">{{ i }}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
title: '只渲染一次的标题',
msg: '这是一条消息',
list: [1, 2, 3]
}
}
}
</script>
v-cloak
vue
<template>
<!-- 防止页面加载时显示未编译的Mustache标签 -->
<div v-cloak>
{{ message }}
</div>
</template>
<style>
[v-cloak] {
display: none;
}
</style>
<script>
export default {
data() {
return {
message: 'Hello World'
}
}
}
</script>
7. 自定义指令
全局注册
javascript
// main.js
const app = createApp({})
// 注册一个全局自定义指令 v-focus
app.directive('focus', {
// 当被绑定的元素挂载到DOM中时...
mounted(el) {
// 聚焦元素
el.focus()
}
})
局部注册
vue
<template>
<input v-focus />
</template>
<script>
export default {
directives: {
// 在模板中启用v-focus
focus: {
// 指令的定义
mounted(el) {
el.focus()
}
}
}
}
</script>
指令钩子函数
javascript
const myDirective = {
// 在绑定元素的父组件被挂载前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在元素被插入到DOM前调用
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {}
}
高级自定义指令示例
javascript
// 颜色指令
app.directive('color', {
mounted(el, binding) {
el.style.color = binding.value
},
updated(el, binding) {
el.style.color = binding.value
}
})
// 权限指令
app.directive('permission', {
mounted(el, binding) {
const { value } = binding
const roles = ['admin', 'user'] // 从store或其他地方获取用户角色
if (value && value instanceof Array && value.length > 0) {
const permissionRoles = value
const hasPermission = roles.some(role => {
return permissionRoles.includes(role)
})
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
}
}
})
8. 实践
指令实践
-
合理选择v-if和v-show
- 频繁切换使用v-show
- 条件很少改变使用v-if
-
v-for必须使用key
vue<!-- 推荐 --> <li v-for="item in items" :key="item.id">{{ item.name }}</li> <!-- 不推荐 --> <li v-for="item in items">{{ item.name }}</li>
-
避免v-if和v-for同时使用
vue<!-- 不推荐 --> <li v-for="user in users" v-if="user.isActive" :key="user.id"> {{ user.name }} </li> <!-- 推荐:使用computed --> <li v-for="user in activeUsers" :key="user.id"> {{ user.name }} </li>
-
事件处理器优化
vue<!-- 推荐:使用方法 --> <button @click="handleClick">点击</button> <!-- 不推荐:复杂的内联表达式 --> <button @click="items.push({ id: Date.now(), name: 'new' }), updateCount++"> 添加 </button>
Vue.js 指令快速参考表
核心指令对照表
指令 | 作用 | 语法示例 | 修饰符 | 使用场景 |
---|---|---|---|---|
v-model | 双向数据绑定 | <input v-model="message"> |
.lazy .number .trim |
表单输入、自定义组件 |
v-bind | 单向属性绑定 | :src="url" :class="{active: isActive}" |
无 | 动态属性、样式、类名 |
v-if | 条件渲染(销毁/创建) | <div v-if="show">content</div> |
无 | 条件很少改变的元素 |
v-else-if | 多条件渲染 | <div v-else-if="type === 'A'">A</div> |
无 | 多分支条件判断 |
v-else | 否则渲染 | <div v-else>default</div> |
无 | 条件渲染的最后分支 |
v-show | 条件显示(CSS display) | <div v-show="visible">content</div> |
无 | 频繁切换显示/隐藏 |
v-for | 列表渲染 | <li v-for="item in items" :key="item.id"> |
无 | 数组、对象、数字遍历 |
v-on | 事件监听 | @click="handler" @keyup.enter="submit" |
.stop .prevent .once 等 |
用户交互事件处理 |
v-text | 更新文本内容 | <span v-text="message"></span> |
无 | 纯文本显示 |
v-html | 更新HTML内容 | <div v-html="htmlContent"></div> |
无 | 动态HTML内容 |
v-pre | 跳过编译 | <span v-pre>{``{ raw }}</span> |
无 | 显示原始模板语法 |
v-once | 只渲染一次 | <h1 v-once>{``{ title }}</h1> |
无 | 静态内容性能优化 |
v-cloak | 隐藏未编译模板 | <div v-cloak>{``{ message }}</div> |
无 | 防止模板闪烁 |
常用修饰符详解
v-model 修饰符
修饰符 | 说明 | 示例 |
---|---|---|
.lazy |
在 change 事件触发时同步 | <input v-model.lazy="msg"> |
.number |
自动转换为数字类型 | <input v-model.number="age"> |
.trim |
自动去除首尾空格 | <input v-model.trim="msg"> |
v-on 事件修饰符
修饰符 | 说明 | 示例 |
---|---|---|
.stop |
阻止事件冒泡 | @click.stop="doThis" |
.prevent |
阻止默认行为 | @submit.prevent="onSubmit" |
.capture |
使用事件捕获模式 | @click.capture="doThis" |
.self |
只在事件目标是元素自身时触发 | @click.self="doThat" |
.once |
事件只触发一次 | @click.once="doThis" |
.passive |
立即触发默认行为 | @scroll.passive="onScroll" |
v-on 按键修饰符
修饰符 | 说明 | 示例 |
---|---|---|
.enter |
回车键 | @keyup.enter="submit" |
.tab |
Tab键 | @keyup.tab="nextField" |
.delete |
删除键 | @keyup.delete="deleteItem" |
.esc |
Escape键 | @keyup.esc="cancel" |
.space |
空格键 | @keyup.space="play" |
.up/.down/.left/.right |
方向键 | @keyup.up="moveUp" |
.ctrl/.alt/.shift/.meta |
系统修饰键 | @keyup.ctrl.enter="save" |
使用场景对比
v-if vs v-show
特性 | v-if | v-show |
---|---|---|
渲染方式 | 条件性渲染(真正的删除/创建) | 基于CSS display切换 |
切换开销 | 高(重新渲染) | 低(只改变CSS) |
初始开销 | 低(条件为假时不渲染) | 高(总是渲染) |
适用场景 | 条件很少改变 | 频繁切换 |
生命周期 | 会触发组件的生命周期 | 不会触发生命周期 |
v-for 使用要点
遍历类型 | 语法 | 参数说明 |
---|---|---|
数组 | v-for="item in items" |
item: 数组元素 |
数组+索引 | v-for="(item, index) in items" |
item: 元素, index: 索引 |
对象 | v-for="value in object" |
value: 属性值 |
对象+键名 | v-for="(value, key) in object" |
value: 值, key: 键名 |
对象+键名+索引 | v-for="(value, key, index) in object" |
value: 值, key: 键名, index: 索引 |
数字 | v-for="n in 10" |
n: 1到10的数字 |
最佳实践速查
✅ 推荐做法
- v-for 必须使用
:key
- 使用计算属性代替复杂的模板表达式
- 事件处理器使用方法而非内联表达式
- 合理选择 v-if 和 v-show
- 使用缩写语法(
:
代替v-bind:
,@
代替v-on:
)
❌ 避免做法
- v-for 和 v-if 在同一元素上使用
- 使用数组索引作为 key(除非必要)
- 在模板中编写复杂逻辑
- 忘记使用事件修饰符优化性能
- 滥用 v-html(XSS风险)
自定义指令语法
注册方式
javascript
// 全局注册
app.directive('focus', {
mounted(el) { el.focus() }
})
// 局部注册
directives: {
focus: {
mounted(el) { el.focus() }
}
}
钩子函数
钩子 | 触发时机 |
---|---|
beforeMount |
绑定元素的父组件被挂载前 |
mounted |
元素被插入到DOM后 |
beforeUpdate |
绑定元素的父组件更新前 |
updated |
父组件及所有子节点都更新后 |
beforeUnmount |
绑定元素的父组件卸载前 |
unmounted |
绑定元素的父组件卸载后 |
参数说明
javascript
directive(el, binding, vnode, prevVnode) {
// el: 绑定的元素
// binding: { value, oldValue, arg, modifiers, instance, dir }
// vnode: 虚拟节点
// prevVnode: 之前的虚拟节点
}