directives.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  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 } from '@vue/shared'
  13. import { warn } from './warning'
  14. import { ComponentInternalInstance, Data } from './component'
  15. import { currentRenderingInstance } from './componentRenderContext'
  16. import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
  17. import { ComponentPublicInstance } from './componentPublicInstance'
  18. import { mapCompatDirectiveHook } from './compat/customDirective'
  19. import { pauseTracking, resetTracking } from '@vue/reactivity'
  20. export interface DirectiveBinding<V = any> {
  21. instance: ComponentPublicInstance | null
  22. value: V
  23. oldValue: V | null
  24. arg?: string
  25. modifiers: DirectiveModifiers
  26. dir: ObjectDirective<any, V>
  27. }
  28. export type DirectiveHook<T = any, Prev = VNode<any, T> | null, V = any> = (
  29. el: T,
  30. binding: DirectiveBinding<V>,
  31. vnode: VNode<any, T>,
  32. prevVNode: Prev
  33. ) => void
  34. export type SSRDirectiveHook = (
  35. binding: DirectiveBinding,
  36. vnode: VNode
  37. ) => Data | undefined
  38. export interface ObjectDirective<T = any, V = any> {
  39. created?: DirectiveHook<T, null, V>
  40. beforeMount?: DirectiveHook<T, null, V>
  41. mounted?: DirectiveHook<T, null, V>
  42. beforeUpdate?: DirectiveHook<T, VNode<any, T>, V>
  43. updated?: DirectiveHook<T, VNode<any, T>, V>
  44. beforeUnmount?: DirectiveHook<T, null, V>
  45. unmounted?: DirectiveHook<T, null, V>
  46. getSSRProps?: SSRDirectiveHook
  47. }
  48. export type FunctionDirective<T = any, V = any> = DirectiveHook<T, any, V>
  49. export type Directive<T = any, V = any> =
  50. | ObjectDirective<T, V>
  51. | FunctionDirective<T, V>
  52. export type DirectiveModifiers = Record<string, boolean>
  53. const isBuiltInDirective = /*#__PURE__*/ makeMap(
  54. 'bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text'
  55. )
  56. export function validateDirectiveName(name: string) {
  57. if (isBuiltInDirective(name)) {
  58. warn('Do not use built-in directive ids as custom directive id: ' + name)
  59. }
  60. }
  61. // Directive, value, argument, modifiers
  62. export type DirectiveArguments = Array<
  63. | [Directive]
  64. | [Directive, any]
  65. | [Directive, any, string]
  66. | [Directive, any, string, DirectiveModifiers]
  67. >
  68. /**
  69. * Adds directives to a VNode.
  70. */
  71. export function withDirectives<T extends VNode>(
  72. vnode: T,
  73. directives: DirectiveArguments
  74. ): T {
  75. const internalInstance = currentRenderingInstance
  76. if (internalInstance === null) {
  77. __DEV__ && warn(`withDirectives can only be used inside render functions.`)
  78. return vnode
  79. }
  80. const instance = internalInstance.proxy
  81. const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])
  82. for (let i = 0; i < directives.length; i++) {
  83. let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]
  84. if (isFunction(dir)) {
  85. dir = {
  86. mounted: dir,
  87. updated: dir
  88. } as ObjectDirective
  89. }
  90. bindings.push({
  91. dir,
  92. instance,
  93. value,
  94. oldValue: void 0,
  95. arg,
  96. modifiers
  97. })
  98. }
  99. return vnode
  100. }
  101. export function invokeDirectiveHook(
  102. vnode: VNode,
  103. prevVNode: VNode | null,
  104. instance: ComponentInternalInstance | null,
  105. name: keyof ObjectDirective
  106. ) {
  107. const bindings = vnode.dirs!
  108. const oldBindings = prevVNode && prevVNode.dirs!
  109. for (let i = 0; i < bindings.length; i++) {
  110. const binding = bindings[i]
  111. if (oldBindings) {
  112. binding.oldValue = oldBindings[i].value
  113. }
  114. let hook = binding.dir[name] as DirectiveHook | DirectiveHook[] | undefined
  115. if (__COMPAT__ && !hook) {
  116. hook = mapCompatDirectiveHook(name, binding.dir, instance)
  117. }
  118. if (hook) {
  119. // disable tracking inside all lifecycle hooks
  120. // since they can potentially be called inside effects.
  121. pauseTracking()
  122. callWithAsyncErrorHandling(hook, instance, ErrorCodes.DIRECTIVE_HOOK, [
  123. vnode.el,
  124. binding,
  125. vnode,
  126. prevVNode
  127. ])
  128. resetTracking()
  129. }
  130. }
  131. }