指令编译原理
📖 本节总结
指令在编译阶段被转换为具体的 DOM 操作或事件绑定。
指令的编译过程
模板
vue
<template>
<div v-bind:id="id" v-on:click="handleClick" v-if="show">
{{ message }}
</div>
</template>1
2
3
4
5
2
3
4
5
编译后
javascript
// 条件判断
if (show) {
// 创建元素
const el = document.createElement('div')
// 设置属性
el.setAttribute('id', id)
// 绑定事件
el.addEventListener('click', handleClick)
// 设置文本
el.textContent = message
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
v-bind 编译
javascript
// v-bind:href -> setAttribute
// v-bind:value -> setAttribute
// v-bind:[name] -> setAttribute
// 编译时确定
h('a', { href: url })
// 动态绑定
h('a', {
[`:${dynamicAttr}`]: value
})1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
编译优化
javascript
// 静态属性,直接设置
el.setAttribute('id', 'app')
// 动态属性,需要追踪
const update = () => {
el.setAttribute('data-name', state.name)
}1
2
3
4
5
6
2
3
4
5
6
v-on 编译
javascript
// v-on:click -> addEventListener
// v-on:input -> addEventListener
h('button', {
onClick: handleClick,
onMouseenter: handleMouseEnter
})1
2
3
4
5
6
2
3
4
5
6
事件修饰符
javascript
// .stop -> event.stopPropagation()
// .prevent -> event.preventDefault()
// .capture -> capture: true
// .self -> 检查 event.target
// .once -> { once: true }
// 编译后
h('div', {
onClick: (e) => {
e.stopPropagation()
handleClick()
}
})1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
按键修饰符
javascript
// .enter -> 检查 keyCode === 13
// .space -> 检查 keyCode === 32
// .ctrl -> 检查 ctrlKey
h('input', {
onKeydown: (e) => {
if (e.keyCode === 13) {
handleEnter()
}
}
})1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
v-if 编译
javascript
// v-if="show" -> 三元表达式或条件渲染
// 编译后
show
? h('div', message)
: createCommentVNode('v-if')1
2
3
4
5
2
3
4
5
v-else-if / v-else
javascript
// 编译后使用 renderSwitch
function render() {
if (type === 'A') {
return h('div', 'A')
} else if (type === 'B') {
return h('div', 'B')
} else {
return h('div', 'C')
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
v-for 编译
javascript
// v-for="item in items" :key="item.id"
items.map(item => {
return h('div', { key: item.id }, item.name)
})1
2
3
4
2
3
4
编译后
javascript
// 使用 withKeys 包装
function render(_ctx) {
return _renderList(_ctx.items, (item) => {
return h('div', { key: item.id }, item.name)
})
}1
2
3
4
5
6
2
3
4
5
6
v-show 编译
javascript
// v-show="show" -> display 样式
h('div', {
style: show ? {} : { display: 'none' }
})1
2
3
4
2
3
4
完整的编译示例
javascript
// 模板
<template>
<div
v-if="visible"
:class="className"
:style="style"
@click="handleClick"
>
{{ message }}
</div>
</template>
// 编译后
function render(_ctx) {
return _ctx.visible
? withDirectives(
h('div', {
class: _ctx.className,
style: _ctx.style,
onClick: _ctx.handleClick
}, _ctx.message),
[[vShow, _ctx.visible]])
: createCommentVNode('v-if')
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
withDirectives
javascript
// 用于给 vnode 添加指令
export function withDirectives(vnode, directives) {
// vnode 是虚拟节点
// directives 是指令数组 [[指令, 值], [指令, 值]]
const internalInstance = currentRenderingInstance
if (internalInstance) {
vnode.directives = vnode.directives || []
vnode.directives.push(...directives)
}
return vnode
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
指令的运行时
javascript
// packages/runtime-dom/src/directives/vShow.ts
export const vShow = {
// 指令挂载时
beforeMount(el, { value }, { transition }) {
// 保存原始 display 值
el._vod = el.style.display
if (value) {
// 显示
transition ? transition.enter(el) : removeDisplay(el)
} else {
// 隐藏
el.style.display = 'none'
}
},
// 更新时
updated(el, { value, oldValue }, { transition }) {
if (value === oldValue) return
transition ? transition.leave(el, () => {
if (value) {
removeDisplay(el)
} else {
el.style.display = 'none'
}
}) : null
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
总结
| 编译阶段 | 产物 |
|---|---|
| v-bind | setAttribute / 直接属性 |
| v-on | addEventListener |
| v-if | 条件表达式 |
| v-for | map 遍历 |
| v-show | display 样式 |
| v-model | v-bind + v-on |