Vue – 内置指令源码 🔗
v-once 🔗
v-once
在vue
中用于缓存dom
实现,在加了这个指令的节点上,只会渲染一次,后续dom
更新时会直接使用缓存
这里用一个WeakSet
储存缓存节点,并且设置转换上下文的inVOnce
属性为true
返回一个闭包函数, 函数内设置inVOnce
为false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| const seen = new WeakSet()
export const transformOnce: NodeTransform = (node, context) => {
if (node.type === NodeTypes.ELEMENT && findDir(node, 'once', true)) {
if (seen.has(node) || context.inVOnce || context.inSSR) {
return
}
seen.add(node)
context.inVOnce = true
context.helper(SET_BLOCK_TRACKING)
return () => {
context.inVOnce = false
const cur = context.currentNode as ElementNode | IfNode | ForNode
if (cur.codegenNode) {
cur.codegenNode = context.cache(cur.codegenNode, true /* isVNode */)
}
}
}
}
|
v-if 🔗
转化节点中if
、else
、else-if
指令
1
2
3
4
5
6
7
8
| export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
(node, dir, context) => {
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
// main code
}
}
)
|
这里实现了一个高阶函数
传入的可以是一个字符串,也可以是正则表达式, 目的是实现一个匹配函数
返回一个闭包,闭包中遍历node
节点属性,遍历节点的props
,找到目标节点并执行回调函数
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
27
28
29
30
31
32
33
| export function createStructuralDirectiveTransform(
name: string | RegExp,
fn: StructuralDirectiveTransform
): NodeTransform {
const matches = isString(name)
? (n: string) => n === name
: (n: string) => name.test(n)
return (node, context) => {
if (node.type === NodeTypes.ELEMENT) {
const { props } = node
// structural directive transforms are not concerned with slots
// as they are handled separately in vSlot.ts
if (node.tagType === ElementTypes.TEMPLATE && props.some(isVSlot)) {
return
}
const exitFns = []
for (let i = 0; i < props.length; i++) {
const prop = props[i]
if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {
// structural directives are removed to avoid infinite recursion
// also we remove them *before* applying so that it can further
// traverse itself in case it moves the node around
props.splice(i, 1)
i--
const onExit = fn(node, prop, context)
if (onExit) exitFns.push(onExit)
}
}
return exitFns
}
}
}
|
processIf 🔗
如果指令属性名是if
,这里会创建一个ifNode
,并且替换原节点
1.处理v-if节点 🔗
1
2
3
4
5
6
7
8
9
10
11
12
| if (dir.name === 'if') {
const branch = createIfBranch(node, dir)
const ifNode: IfNode = {
type: NodeTypes.IF,
loc: node.loc,
branches: [branch]
}
context.replaceNode(ifNode)
if (processCodegen) {
return processCodegen(ifNode, branch, true)
}
}
|
createIfBranch 🔗
这里创建一个if
节点,如果是template
上的if
指令,还会做其他的记录
1
2
3
4
5
6
7
8
9
10
11
| function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
const isTemplateIf = node.tagType === ElementTypes.TEMPLATE
return {
type: NodeTypes.IF_BRANCH,
loc: node.loc,
condition: dir.name === 'else' ? undefined : dir.exp,
children: isTemplateIf && !findDir(node, 'for') ? node.children : [node],
userKey: findProp(node, `key`),
isTemplateIf
}
}
|
处理v-else、v-else-if节点 🔗
找出父所有的子节点
根据indexOf方法找到索引
从当前索引向前遍历
如果是注释节点,删除
1
2
3
4
5
| if (sibling && sibling.type === NodeTypes.COMMENT) {
context.removeNode(sibling)
__DEV__ && comments.unshift(sibling)
continue
}
|
如果是文本节点,并且文本节点含有字符,删除
1
2
3
4
5
6
7
8
| if (
sibling &&
sibling.type === NodeTypes.TEXT &&
!sibling.content.trim().length
) {
context.removeNode(sibling)
continue
}
|
如果兄弟节点是if节点,这里才开始检查正文
如果当前节点指令是else-if
,这里用来判断v-else-if
必须在v-else
前面
1
2
3
4
5
6
7
8
| if (
dir.name === 'else-if' &&
sibling.branches[sibling.branches.length - 1].condition === undefined
) {
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
)
}
|
移除当前节点
增加到if节点的分支branchs
中
1
| sibling.branches.push(branch)
|
处理转化节点和他的子节点
1
2
3
4
5
6
7
8
9
| const onExit = processCodegen && processCodegen(sibling, branch, false)
// since the branch was removed, it will not be traversed.
// make sure to traverse here.
traverseNode(branch, context)
// call on exit
if (onExit) onExit()
// make sure to reset currentNode after traversal to indicate this
// node has been removed.
context.currentNode = null
|