directives.ts 4.2 KB

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