vue3-yuanma/packages/runtime-core/src/directives.ts

167 lines
4.4 KiB
TypeScript
Raw Normal View History

2019-08-31 20:36:36 +00:00
/**
Runtime helper for applying directives to a vnode. Example usage:
const comp = resolveComponent('comp')
const foo = resolveDirective('foo')
const bar = resolveDirective('bar')
return withDirectives(h(comp), [
2019-08-31 20:36:36 +00:00
[foo, this.x],
[bar, this.y]
2019-09-03 22:11:04 +00:00
])
2019-08-31 20:36:36 +00:00
*/
import { VNode, VNodeHook } from './vnode'
import { isFunction, EMPTY_OBJ, makeMap, EMPTY_ARR } from '@vue/shared'
2019-08-31 20:36:36 +00:00
import { warn } from './warning'
import { ComponentInternalInstance, Data } from './component'
import { currentRenderingInstance } from './componentRenderUtils'
2019-09-06 16:58:31 +00:00
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
import { ComponentPublicInstance } from './componentProxy'
2019-08-31 20:36:36 +00:00
2019-09-02 16:09:29 +00:00
export interface DirectiveBinding {
instance: ComponentPublicInstance | null
value: any
oldValue: any
2019-08-31 20:36:36 +00:00
arg?: string
modifiers: DirectiveModifiers
dir: ObjectDirective
2019-08-31 20:36:36 +00:00
}
export type DirectiveHook<T = any> = (
el: T,
2019-08-31 20:36:36 +00:00
binding: DirectiveBinding,
vnode: VNode<any, T>,
2019-10-14 02:40:29 +00:00
prevVNode: VNode<any, T> | null
2019-08-31 20:36:36 +00:00
) => void
export type SSRDirectiveHook = (
binding: DirectiveBinding,
vnode: VNode
) => Data | undefined
export interface ObjectDirective<T = any> {
beforeMount?: DirectiveHook<T>
mounted?: DirectiveHook<T>
beforeUpdate?: DirectiveHook<T>
updated?: DirectiveHook<T>
beforeUnmount?: DirectiveHook<T>
unmounted?: DirectiveHook<T>
getSSRProps?: SSRDirectiveHook
2019-08-31 20:36:36 +00:00
}
export type FunctionDirective<T = any> = DirectiveHook<T>
export type Directive<T = any> = ObjectDirective<T> | FunctionDirective<T>
export type DirectiveModifiers = Record<string, boolean>
2019-08-31 20:36:36 +00:00
export type VNodeDirectiveData = [
unknown,
string | undefined,
DirectiveModifiers
]
2019-08-31 20:36:36 +00:00
const isBuiltInDirective = /*#__PURE__*/ makeMap(
'bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text'
)
export function validateDirectiveName(name: string) {
if (isBuiltInDirective(name)) {
warn('Do not use built-in directive ids as custom directive id: ' + name)
}
}
const directiveToVnodeHooksMap = /*#__PURE__*/ [
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeUnmount',
'unmounted'
].reduce(
(map, key: keyof ObjectDirective) => {
const vnodeKey = `onVnode` + key[0].toUpperCase() + key.slice(1)
const vnodeHook = (vnode: VNode, prevVnode: VNode | null) => {
const bindings = vnode.dirs!
const prevBindings = prevVnode ? prevVnode.dirs! : EMPTY_ARR
for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i]
const hook = binding.dir[key] as DirectiveHook
if (hook != null) {
if (prevVnode != null) {
binding.oldValue = prevBindings[i].value
}
hook(vnode.el, binding, vnode, prevVnode)
}
2019-08-31 20:36:36 +00:00
}
}
map[key] = [vnodeKey, vnodeHook]
return map
},
{} as Record<string, [string, Function]>
)
2019-08-31 20:36:36 +00:00
2019-09-02 16:09:29 +00:00
// Directive, value, argument, modifiers
2019-09-06 16:58:31 +00:00
export type DirectiveArguments = Array<
2019-09-02 16:09:29 +00:00
| [Directive]
| [Directive, any]
| [Directive, any, string]
| [Directive, any, string, DirectiveModifiers]
>
2019-08-31 20:36:36 +00:00
2019-10-26 20:32:27 +00:00
export function withDirectives<T extends VNode>(
vnode: T,
directives: DirectiveArguments
): T {
const internalInstance = currentRenderingInstance
if (internalInstance === null) {
__DEV__ && warn(`withDirectives can only be used inside render functions.`)
return vnode
}
const instance = internalInstance.proxy
const props = vnode.props || (vnode.props = {})
const bindings = vnode.dirs || (vnode.dirs = new Array(directives.length))
const injected: Record<string, true> = {}
for (let i = 0; i < directives.length; i++) {
let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]
if (isFunction(dir)) {
dir = {
mounted: dir,
updated: dir
} as ObjectDirective
}
bindings[i] = {
dir,
instance,
value,
oldValue: void 0,
arg,
modifiers
}
// inject onVnodeXXX hooks
for (const key in dir) {
const mapped = directiveToVnodeHooksMap[key]
if (mapped && !injected[key]) {
const { 0: hookName, 1: hook } = mapped
const existing = props[hookName]
props[hookName] = existing ? [].concat(existing, hook as any) : hook
injected[key] = true
}
2019-08-31 20:36:36 +00:00
}
}
return vnode
}
2019-08-31 21:06:39 +00:00
export function invokeDirectiveHook(
hook: VNodeHook | VNodeHook[],
2019-09-06 16:58:31 +00:00
instance: ComponentInternalInstance | null,
2019-09-02 16:09:29 +00:00
vnode: VNode,
prevVNode: VNode | null = null
2019-08-31 21:06:39 +00:00
) {
callWithAsyncErrorHandling(hook, instance, ErrorCodes.DIRECTIVE_HOOK, [
vnode,
prevVNode
])
2019-08-31 21:06:39 +00:00
}