Vue.js 指令系统完全指南:深入理解 v- 指令
Vue.js 的指令系统是其最强大的特性之一,通过以
v-
开头的特殊属性,我们可以在模板中声明式地绑定底层Vue实例的数据。本文将深入讲解Vue中最重要的指令,帮助掌握Vue的核心功能。
文章目录
- 1. v-model:双向数据绑定的核心
- 基本用法
- 修饰符
- 自定义组件中的 v-model
- 2. v-bind:属性绑定的万能钥匙
- 基本用法
- 动态属性名
- 3. v-if / v-else-if / v-else:条件渲染
- 基本用法
- v-if vs v-show
- 4. v-for:列表渲染
- 基本用法
- 维护状态(key的重要性)
- 5. v-on:事件处理
- 基本用法
- 事件修饰符
- 按键修饰符
- 6. 其他重要指令
- v-text 和 v-html
- v-show
- v-pre
- v-once
- v-cloak
- 7. 自定义指令
- 全局注册
- 局部注册
- 指令钩子函数
- 高级自定义指令示例
- 8. 实践
- 指令实践
- Vue.js 指令快速参考表
- 核心指令对照表
- 常用修饰符详解
- v-model 修饰符
- v-on 事件修饰符
- v-on 按键修饰符
- 使用场景对比
- v-if vs v-show
- v-for 使用要点
- 最佳实践速查
- ✅ 推荐做法
- ❌ 避免做法
- 自定义指令语法
- 注册方式
- 钩子函数
- 参数说明
1. v-model:双向数据绑定的核心
基本用法
v-model
是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 提供了三个有用的修饰符:
<!-- .lazy - 在 change 事件而非 input 事件触发时更新 -->
<input v-model.lazy="msg"><!-- .number - 自动将用户输入转换为数值类型 -->
<input v-model.number="age" type="number"><!-- .trim - 自动过滤用户输入的首尾空白字符 -->
<input v-model.trim="msg">
自定义组件中的 v-model
<!-- 父组件 -->
<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 到表达式。
<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>
动态属性名
<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:条件渲染
基本用法
这些指令用于条件性地渲染元素。
<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
<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
指令用于基于源数据多次渲染元素或模板块。
<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的重要性)
<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代码。
<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事件细节。
<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>
按键修饰符
<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
<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
<template><div><!-- 根据表达式的真假值,切换元素的display CSS属性 --><h1 v-show="ok">Hello!</h1></div>
</template><script>
export default {data() {return {ok: true}}
}
</script>
v-pre
<template><div><!-- 跳过这个元素和它的子元素的编译过程 --><span v-pre>{{ this will not be compiled }}</span></div>
</template>
v-once
<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
<template><!-- 防止页面加载时显示未编译的Mustache标签 --><div v-cloak>{{ message }}</div>
</template><style>
[v-cloak] {display: none;
}
</style><script>
export default {data() {return {message: 'Hello World'}}
}
</script>
7. 自定义指令
全局注册
// main.js
const app = createApp({})// 注册一个全局自定义指令 v-focus
app.directive('focus', {// 当被绑定的元素挂载到DOM中时...mounted(el) {// 聚焦元素el.focus()}
})
局部注册
<template><input v-focus />
</template><script>
export default {directives: {// 在模板中启用v-focusfocus: {// 指令的定义mounted(el) {el.focus()}}}
}
</script>
指令钩子函数
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) {}
}
高级自定义指令示例
// 颜色指令
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 } = bindingconst roles = ['admin', 'user'] // 从store或其他地方获取用户角色if (value && value instanceof Array && value.length > 0) {const permissionRoles = valueconst 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
<!-- 推荐 --> <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同时使用
<!-- 不推荐 --> <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>
-
事件处理器优化
<!-- 推荐:使用方法 --> <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风险)
自定义指令语法
注册方式
// 全局注册
app.directive('focus', {mounted(el) { el.focus() }
})// 局部注册
directives: {focus: {mounted(el) { el.focus() }}
}
钩子函数
钩子 | 触发时机 |
---|---|
beforeMount | 绑定元素的父组件被挂载前 |
mounted | 元素被插入到DOM后 |
beforeUpdate | 绑定元素的父组件更新前 |
updated | 父组件及所有子节点都更新后 |
beforeUnmount | 绑定元素的父组件卸载前 |
unmounted | 绑定元素的父组件卸载后 |
参数说明
directive(el, binding, vnode, prevVnode) {// el: 绑定的元素// binding: { value, oldValue, arg, modifiers, instance, dir }// vnode: 虚拟节点// prevVnode: 之前的虚拟节点
}