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