directives.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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. getComponentPublicInstance,
  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, traverse } from '@vue/reactivity'
  24. export interface DirectiveBinding<
  25. Value = any,
  26. Modifiers extends string = string,
  27. Arg extends string = string,
  28. > {
  29. instance: ComponentPublicInstance | Record<string, any> | null
  30. value: Value
  31. oldValue: Value | null
  32. arg?: Arg
  33. modifiers: DirectiveModifiers<Modifiers>
  34. dir: ObjectDirective<any, Value>
  35. }
  36. export type DirectiveHook<
  37. HostElement = any,
  38. Prev = VNode<any, HostElement> | null,
  39. Value = any,
  40. Modifiers extends string = string,
  41. Arg extends string = string,
  42. > = (
  43. el: HostElement,
  44. binding: DirectiveBinding<Value, Modifiers, Arg>,
  45. vnode: VNode<any, HostElement>,
  46. prevVNode: Prev,
  47. ) => void
  48. export type SSRDirectiveHook<
  49. Value = any,
  50. Modifiers extends string = string,
  51. Arg extends string = string,
  52. > = (
  53. binding: DirectiveBinding<Value, Modifiers, Arg>,
  54. vnode: VNode,
  55. ) => Data | undefined
  56. export interface ObjectDirective<
  57. HostElement = any,
  58. Value = any,
  59. Modifiers extends string = string,
  60. Arg extends string = string,
  61. > {
  62. /**
  63. * @internal without this, ts-expect-error in directives.test-d.ts somehow
  64. * fails when running tsc, but passes in IDE and when testing against built
  65. * dts. Could be a TS bug.
  66. */
  67. __mod?: Modifiers
  68. created?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
  69. beforeMount?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
  70. mounted?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
  71. beforeUpdate?: DirectiveHook<
  72. HostElement,
  73. VNode<any, HostElement>,
  74. Value,
  75. Modifiers,
  76. Arg
  77. >
  78. updated?: DirectiveHook<
  79. HostElement,
  80. VNode<any, HostElement>,
  81. Value,
  82. Modifiers,
  83. Arg
  84. >
  85. beforeUnmount?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
  86. unmounted?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
  87. getSSRProps?: SSRDirectiveHook<Value, Modifiers, Arg>
  88. deep?: boolean
  89. }
  90. export type FunctionDirective<
  91. HostElement = any,
  92. V = any,
  93. Modifiers extends string = string,
  94. Arg extends string = string,
  95. > = DirectiveHook<HostElement, any, V, Modifiers, Arg>
  96. export type Directive<
  97. HostElement = any,
  98. Value = any,
  99. Modifiers extends string = string,
  100. Arg extends string = string,
  101. > =
  102. | ObjectDirective<HostElement, Value, Modifiers, Arg>
  103. | FunctionDirective<HostElement, Value, Modifiers, Arg>
  104. export type DirectiveModifiers<K extends string = string> = Partial<
  105. Record<K, boolean>
  106. >
  107. export function validateDirectiveName(name: string): void {
  108. if (isBuiltInDirective(name)) {
  109. warn('Do not use built-in directive ids as custom directive id: ' + name)
  110. }
  111. }
  112. // Directive, value, argument, modifiers
  113. export type DirectiveArguments = Array<
  114. | [Directive | undefined]
  115. | [Directive | undefined, any]
  116. | [Directive | undefined, any, string]
  117. | [Directive | undefined, any, string | undefined, DirectiveModifiers]
  118. >
  119. /**
  120. * Adds directives to a VNode.
  121. */
  122. export function withDirectives<T extends VNode>(
  123. vnode: T,
  124. directives: DirectiveArguments,
  125. ): T {
  126. if (currentRenderingInstance === null) {
  127. __DEV__ && warn(`withDirectives can only be used inside render functions.`)
  128. return vnode
  129. }
  130. const instance = getComponentPublicInstance(currentRenderingInstance)
  131. const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])
  132. for (let i = 0; i < directives.length; i++) {
  133. let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]
  134. if (dir) {
  135. if (isFunction(dir)) {
  136. dir = {
  137. mounted: dir,
  138. updated: dir,
  139. } as ObjectDirective
  140. }
  141. if (dir.deep) {
  142. traverse(value)
  143. }
  144. bindings.push({
  145. dir,
  146. instance,
  147. value,
  148. oldValue: void 0,
  149. arg,
  150. modifiers,
  151. })
  152. }
  153. }
  154. return vnode
  155. }
  156. export function invokeDirectiveHook(
  157. vnode: VNode,
  158. prevVNode: VNode | null,
  159. instance: ComponentInternalInstance | null,
  160. name: keyof ObjectDirective,
  161. ): void {
  162. const bindings = vnode.dirs!
  163. const oldBindings = prevVNode && prevVNode.dirs!
  164. for (let i = 0; i < bindings.length; i++) {
  165. const binding = bindings[i]
  166. if (oldBindings) {
  167. binding.oldValue = oldBindings[i].value
  168. }
  169. let hook = binding.dir[name] as DirectiveHook | DirectiveHook[] | undefined
  170. if (__COMPAT__ && !hook) {
  171. hook = mapCompatDirectiveHook(name, binding.dir, instance)
  172. }
  173. if (hook) {
  174. // disable tracking inside all lifecycle hooks
  175. // since they can potentially be called inside effects.
  176. pauseTracking()
  177. callWithAsyncErrorHandling(hook, instance, ErrorCodes.DIRECTIVE_HOOK, [
  178. vnode.el,
  179. binding,
  180. vnode,
  181. prevVNode,
  182. ])
  183. resetTracking()
  184. }
  185. }
  186. }