directives.ts 5.4 KB

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