directives.ts 3.6 KB

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