directives.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. /**
  2. Runtime helper for applying directives to a vnode. Example usage:
  3. const comp = resolveComponent('comp')
  4. const foo = resolveDirective('foo')
  5. const bar = resolveDirective('bar')
  6. return withDirectives(h(comp), [
  7. [foo, this.x],
  8. [bar, this.y]
  9. ])
  10. */
  11. import { VNode } from './vnode'
  12. import { isFunction, EMPTY_OBJ, makeMap, EMPTY_ARR } from '@vue/shared'
  13. import { warn } from './warning'
  14. import { ComponentInternalInstance } from './component'
  15. import { currentRenderingInstance } from './componentRenderUtils'
  16. import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
  17. import { ComponentPublicInstance } from './componentProxy'
  18. export interface DirectiveBinding {
  19. instance: ComponentPublicInstance | null
  20. value: any
  21. oldValue: any
  22. arg?: string
  23. modifiers: DirectiveModifiers
  24. dir: ObjectDirective
  25. }
  26. export type DirectiveHook<T = any> = (
  27. el: T,
  28. binding: DirectiveBinding,
  29. vnode: VNode<any, T>,
  30. prevVNode: VNode<any, T> | null
  31. ) => void
  32. export interface ObjectDirective<T = any> {
  33. beforeMount?: DirectiveHook<T>
  34. mounted?: DirectiveHook<T>
  35. beforeUpdate?: DirectiveHook<T>
  36. updated?: DirectiveHook<T>
  37. beforeUnmount?: DirectiveHook<T>
  38. unmounted?: DirectiveHook<T>
  39. }
  40. export type FunctionDirective<T = any> = DirectiveHook<T>
  41. export type Directive<T = any> = ObjectDirective<T> | FunctionDirective<T>
  42. export type DirectiveModifiers = Record<string, boolean>
  43. export type VNodeDirectiveData = [
  44. unknown,
  45. string | undefined,
  46. DirectiveModifiers
  47. ]
  48. const isBuiltInDirective = /*#__PURE__*/ makeMap(
  49. 'bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text'
  50. )
  51. export function validateDirectiveName(name: string) {
  52. if (isBuiltInDirective(name)) {
  53. warn('Do not use built-in directive ids as custom directive id: ' + name)
  54. }
  55. }
  56. const directiveToVnodeHooksMap = /*#__PURE__*/ [
  57. 'beforeMount',
  58. 'mounted',
  59. 'beforeUpdate',
  60. 'updated',
  61. 'beforeUnmount',
  62. 'unmounted'
  63. ].reduce(
  64. (map, key: keyof ObjectDirective) => {
  65. const vnodeKey = `onVnode` + key[0].toUpperCase() + key.slice(1)
  66. const vnodeHook = (vnode: VNode, prevVnode: VNode | null) => {
  67. const bindings = vnode.dirs!
  68. const prevBindings = prevVnode ? prevVnode.dirs! : EMPTY_ARR
  69. for (let i = 0; i < bindings.length; i++) {
  70. const binding = bindings[i]
  71. const hook = binding.dir[key]
  72. if (hook != null) {
  73. if (prevVnode != null) {
  74. binding.oldValue = prevBindings[i].value
  75. }
  76. hook(vnode.el, binding, vnode, prevVnode)
  77. }
  78. }
  79. }
  80. map[key] = [vnodeKey, vnodeHook]
  81. return map
  82. },
  83. {} as Record<string, [string, Function]>
  84. )
  85. // Directive, value, argument, modifiers
  86. export type DirectiveArguments = Array<
  87. | [Directive]
  88. | [Directive, any]
  89. | [Directive, any, string]
  90. | [Directive, any, string, DirectiveModifiers]
  91. >
  92. export function withDirectives<T extends VNode>(
  93. vnode: T,
  94. directives: DirectiveArguments
  95. ): T {
  96. const internalInstance = currentRenderingInstance
  97. if (internalInstance === null) {
  98. __DEV__ && warn(`withDirectives can only be used inside render functions.`)
  99. return vnode
  100. }
  101. const instance = internalInstance.renderProxy
  102. const props = vnode.props || (vnode.props = {})
  103. const bindings = vnode.dirs || (vnode.dirs = new Array(directives.length))
  104. const injected: Record<string, true> = {}
  105. for (let i = 0; i < directives.length; i++) {
  106. let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]
  107. if (isFunction(dir)) {
  108. dir = {
  109. mounted: dir,
  110. updated: dir
  111. } as ObjectDirective
  112. }
  113. bindings[i] = {
  114. dir,
  115. instance,
  116. value,
  117. oldValue: void 0,
  118. arg,
  119. modifiers
  120. }
  121. // inject onVnodeXXX hooks
  122. for (const key in dir) {
  123. if (!injected[key]) {
  124. const { 0: hookName, 1: hook } = directiveToVnodeHooksMap[key]
  125. const existing = props[hookName]
  126. props[hookName] = existing ? [].concat(existing, hook as any) : hook
  127. injected[key] = true
  128. }
  129. }
  130. }
  131. return vnode
  132. }
  133. export function invokeDirectiveHook(
  134. hook: Function | Function[],
  135. instance: ComponentInternalInstance | null,
  136. vnode: VNode,
  137. prevVNode: VNode | null = null
  138. ) {
  139. callWithAsyncErrorHandling(hook, instance, ErrorCodes.DIRECTIVE_HOOK, [
  140. vnode,
  141. prevVNode
  142. ])
  143. }