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: 之前的虛擬節點
}