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

157 lines
4.1 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 } from './vnode'
import { isArray, isFunction, EMPTY_OBJ, makeMap } from '@vue/shared'
2019-08-31 20:36:36 +00:00
import { warn } from './warning'
2019-09-06 16:58:31 +00:00
import { ComponentInternalInstance } from './component'
2019-09-06 15:25:11 +00:00
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 {
2019-09-06 16:58:31 +00:00
instance: ComponentPublicInstance | null
value: any
oldValue: any
2019-08-31 20:36:36 +00:00
arg?: string
modifiers: DirectiveModifiers
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 interface ObjectDirective<T = any> {
beforeMount?: DirectiveHook<T>
mounted?: DirectiveHook<T>
beforeUpdate?: DirectiveHook<T>
updated?: DirectiveHook<T>
beforeUnmount?: DirectiveHook<T>
unmounted?: DirectiveHook<T>
2019-08-31 20:36:36 +00:00
}
export type FunctionDirective<T = any> = DirectiveHook<T>
export type Directive<T = any> = ObjectDirective<T> | FunctionDirective<T>
2019-08-31 20:36:36 +00:00
type DirectiveModifiers = Record<string, boolean>
const valueCache = new WeakMap<Directive, WeakMap<any, any>>()
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)
}
}
2019-08-31 20:36:36 +00:00
function applyDirective(
props: Record<any, any>,
2019-09-06 16:58:31 +00:00
instance: ComponentInternalInstance,
2019-08-31 20:36:36 +00:00
directive: Directive,
value?: any,
arg?: string,
modifiers: DirectiveModifiers = EMPTY_OBJ
2019-08-31 20:36:36 +00:00
) {
2019-10-05 14:09:34 +00:00
let valueCacheForDir = valueCache.get(directive)!
2019-08-31 20:36:36 +00:00
if (!valueCacheForDir) {
valueCacheForDir = new WeakMap<VNode, any>()
valueCache.set(directive, valueCacheForDir)
}
if (isFunction(directive)) {
directive = {
mounted: directive,
updated: directive
} as ObjectDirective
}
2019-08-31 20:36:36 +00:00
for (const key in directive) {
const hook = directive[key as keyof ObjectDirective]!
const hookKey = `onVnode` + key[0].toUpperCase() + key.slice(1)
2019-09-02 16:09:29 +00:00
const vnodeHook = (vnode: VNode, prevVNode: VNode | null) => {
2019-08-31 20:36:36 +00:00
let oldValue
2019-09-02 16:09:29 +00:00
if (prevVNode != null) {
2019-08-31 20:36:36 +00:00
oldValue = valueCacheForDir.get(prevVNode)
valueCacheForDir.delete(prevVNode)
}
valueCacheForDir.set(vnode, value)
hook(
vnode.el,
{
instance: instance.renderProxy,
value,
oldValue,
arg,
modifiers
},
vnode,
prevVNode
)
}
const existing = props[hookKey]
props[hookKey] = existing
2019-10-05 14:09:34 +00:00
? [].concat(existing, vnodeHook as any)
2019-08-31 20:36:36 +00:00
: vnodeHook
}
}
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
export function withDirectives(vnode: VNode, directives: DirectiveArguments) {
2019-08-31 20:36:36 +00:00
const instance = currentRenderingInstance
if (instance !== null) {
vnode.props = vnode.props || {}
2019-08-31 20:36:36 +00:00
for (let i = 0; i < directives.length; i++) {
2019-10-08 16:43:13 +00:00
const [dir, value, arg, modifiers] = directives[i]
applyDirective(vnode.props, instance, dir, value, arg, modifiers)
2019-08-31 20:36:36 +00:00
}
} else if (__DEV__) {
warn(`withDirectives can only be used inside render functions.`)
2019-08-31 20:36:36 +00:00
}
return vnode
}
2019-08-31 21:06:39 +00:00
export function invokeDirectiveHook(
hook: Function | Function[],
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
) {
2019-09-02 16:09:29 +00:00
const args = [vnode, prevVNode]
2019-08-31 21:06:39 +00:00
if (isArray(hook)) {
for (let i = 0; i < hook.length; i++) {
callWithAsyncErrorHandling(
hook[i],
instance,
2019-09-06 16:58:31 +00:00
ErrorCodes.DIRECTIVE_HOOK,
2019-08-31 21:06:39 +00:00
args
)
}
} else if (isFunction(hook)) {
2019-09-06 16:58:31 +00:00
callWithAsyncErrorHandling(hook, instance, ErrorCodes.DIRECTIVE_HOOK, args)
2019-08-31 21:06:39 +00:00
}
}