# Vue3指令是怎么實現的
## 前言
Vue.js作為一款漸進式JavaScript框架,其指令系統是模板語法中最重要的特性之一。指令(Directives)是帶有`v-`前綴的特殊屬性,它們為HTML元素添加特殊行為。本文將深入探討Vue3指令的實現原理,從基礎概念到源碼解析,全面剖析指令系統的工作機制。
## 一、Vue指令基礎
### 1.1 什么是指令
在Vue中,指令是可以附加到DOM元素上的特殊標記,它們以`v-`開頭,用于:
- 響應式地更新DOM
- 綁定元素屬性
- 監聽事件
- 條件渲染
- 循環渲染等
### 1.2 內置指令示例
Vue提供了一系列內置指令:
```html
<!-- 條件渲染 -->
<div v-if="show">顯示內容</div>
<!-- 循環渲染 -->
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
<!-- 事件綁定 -->
<button v-on:click="handleClick">點擊</button>
<!-- 雙向綁定 -->
<input v-model="message">
開發者可以注冊自定義指令:
const app = Vue.createApp({})
// 全局注冊
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// 局部注冊
const directives = {
focus: {
mounted(el) {
el.focus()
}
}
}
Vue模板編譯過程分為三個階段:
在解析階段,編譯器會識別模板中的指令:
// 簡化的解析邏輯
function parseAttribute(node, name, value) {
if (name.startsWith('v-')) {
// 處理指令
const dirName = name.slice(2)
node.directives.push({
name: dirName,
value: value
})
}
}
轉換階段會將指令轉換為對應的JavaScript代碼:
// v-if指令轉換示例
function transformIf(node) {
if (node.directives.some(d => d.name === 'if')) {
return {
type: 'IF',
condition: node.directives.find(d => d.name === 'if').value,
children: [node]
}
}
return node
}
Vue3的指令系統在運行時主要通過以下幾個部分實現:
每個指令可以包含以下鉤子:
interface DirectiveHook {
beforeMount?: (el: any, binding: DirectiveBinding, vnode: VNode) => void
mounted?: (el: any, binding: DirectiveBinding, vnode: VNode) => void
beforeUpdate?: (el: any, binding: DirectiveBinding, vnode: VNode, prevVNode: VNode) => void
updated?: (el: any, binding: DirectiveBinding, vnode: VNode, prevVNode: VNode) => void
beforeUnmount?: (el: any, binding: DirectiveBinding, vnode: VNode) => void
unmounted?: (el: any, binding: DirectiveBinding, vnode: VNode) => void
}
指令接收到的binding對象包含以下屬性:
interface DirectiveBinding {
instance: ComponentPublicInstance | null
value: any
oldValue: any
arg?: string
modifiers: Record<string, boolean>
dir: ObjectDirective<any>
}
// 全局指令注冊實現
function createApp() {
return {
directive(name, directive) {
// 存儲指令
context.directives[name] = normalizeDirective(directive)
return this
}
}
}
function normalizeDirective(dir) {
return typeof dir === 'function'
? { mounted: dir, updated: dir }
: dir
}
v-model是Vue中最復雜的指令之一,其實現涉及:
function genModel(el, value, modifiers) {
const { lazy, number, trim } = modifiers
let event = lazy ? 'change' : 'input'
let valueExpression = `$event.target.value`
if (trim) valueExpression = `${valueExpression}.trim()`
if (number) valueExpression = `_n(${valueExpression})`
const assignment = genAssignmentCode(value, valueExpression)
// 添加事件監聽
addProp(el, 'value', `(${value})`)
addHandler(el, event, assignment, null, true)
}
function vModelDynamic(el, binding, vnode) {
const { type } = el
switch (type) {
case 'checkbox':
return vModelCheckbox(el, binding, vnode)
case 'radio':
return vModelRadio(el, binding, vnode)
case 'select':
return vModelSelect(el, binding, vnode)
default:
return vModelText(el, binding, vnode)
}
}
v-for指令的實現涉及:
function genFor(el) {
const exp = el.for
const alias = el.alias
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
return `_l((${exp}),` +
`function(${alias}${iterator1}${iterator2}){` +
`return ${genElement(el)}` +
`})`
}
function renderList(source, renderItem) {
const ret = []
if (Array.isArray(source)) {
for (let i = 0; i < source.length; i++) {
ret.push(renderItem(source[i], i))
}
} else if (typeof source === 'number') {
for (let i = 0; i < source; i++) {
ret.push(renderItem(i + 1, i))
}
} else if (isObject(source)) {
const keys = Object.keys(source)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
ret.push(renderItem(source[key], key, i))
}
}
return ret
}
function genIf(el) {
return `(${el.if})?${genElement(el)}:_e()`
}
const vShow = {
beforeMount(el, { value }, { transition }) {
el._vod = el.style.display === 'none' ? '' : el.style.display
if (transition && value) {
transition.beforeEnter(el)
}
setDisplay(el, value)
},
updated(el, { value, oldValue }, { transition }) {
if (value === oldValue) return
if (transition) {
if (value) {
transition.beforeEnter(el)
setDisplay(el, true)
transition.enter(el)
} else {
transition.leave(el, () => {
setDisplay(el, false)
})
}
} else {
setDisplay(el, value)
}
}
}
function setDisplay(el, value) {
el.style.display = value ? el._vod : 'none'
}
<div v-example:arg.modifier="value"></div>
對應的binding對象:
{
arg: 'arg',
modifiers: { modifier: true },
value: 'value'
}
<div v-example:[dynamicArg]="value"></div>
多個指令可以組合使用:
<div v-dir1 v-dir2></div>
const clickOutside = {
beforeMount(el, binding) {
el.clickOutsideEvent = event => {
if (!(el === event.target || el.contains(event.target))) {
binding.value(event)
}
}
document.addEventListener('click', el.clickOutsideEvent)
},
unmounted(el) {
document.removeEventListener('click', el.clickOutsideEvent)
}
}
const permission = {
mounted(el, binding) {
const { value } = binding
const permissions = store.getters.permissions
if (!permissions.includes(value)) {
el.parentNode && el.parentNode.removeChild(el)
}
}
}
Vue3在指令系統上做了多項優化:
在組合式API中使用指令:
import { vMyDirective } from './myDirective'
export default {
directives: { vMyDirective },
setup() {
// ...
}
}
為自定義指令添加類型支持:
import { DirectiveBinding } from 'vue'
interface MyDirectiveBinding extends Omit<DirectiveBinding, 'modifiers'> {
modifiers: {
reverse?: boolean
}
}
const myDirective = {
mounted(el: HTMLElement, binding: MyDirectiveBinding) {
// ...
}
}
Vue3的指令系統是其模板功能的核心部分,通過本文的分析,我們可以看到:
理解指令的實現原理有助于我們更好地使用Vue,編寫更高效的代碼,并能夠開發出功能強大的自定義指令來滿足特定需求。
指令鉤子 | 組件鉤子 | 調用時機 |
---|---|---|
beforeMount | beforeMount | 元素被掛載前 |
mounted | mounted | 元素被掛載后 |
beforeUpdate | beforeUpdate | 元素更新前 |
updated | updated | 元素更新后 |
beforeUnmount | beforeUnmount | 元素卸載前 |
unmounted | unmounted | 元素卸載后 |
特性 | Vue2 | Vue3 |
---|---|---|
注冊方式 | Vue.directive | app.directive |
鉤子名稱 | bind | beforeMount |
inserted | mounted | |
update | beforeUpdate (新) | |
componentUpdated | updated | |
unbind | unmounted | |
參數傳遞 | 基本相同 | 基本相同 |
動態參數 | 不支持 | 支持 |
”`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。