directives.ts 4.2 KB

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