rendererTemplateRef.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import type { SuspenseBoundary } from './components/Suspense'
  2. import type { VNode, VNodeNormalizedRef, VNodeNormalizedRefAtom } from './vnode'
  3. import {
  4. EMPTY_OBJ,
  5. ShapeFlags,
  6. hasOwn,
  7. isArray,
  8. isFunction,
  9. isString,
  10. remove,
  11. } from '@vue/shared'
  12. import { isAsyncWrapper } from './apiAsyncComponent'
  13. import { warn } from './warning'
  14. import { isRef, toRaw } from '@vue/reactivity'
  15. import { ErrorCodes, callWithErrorHandling } from './errorHandling'
  16. import type { SchedulerJob } from './scheduler'
  17. import { queuePostRenderEffect } from './renderer'
  18. import { type ComponentOptions, getComponentPublicInstance } from './component'
  19. import { knownTemplateRefs } from './helpers/useTemplateRef'
  20. /**
  21. * Function for handling a template ref
  22. */
  23. export function setRef(
  24. rawRef: VNodeNormalizedRef,
  25. oldRawRef: VNodeNormalizedRef | null,
  26. parentSuspense: SuspenseBoundary | null,
  27. vnode: VNode,
  28. isUnmount = false,
  29. ): void {
  30. if (isArray(rawRef)) {
  31. rawRef.forEach((r, i) =>
  32. setRef(
  33. r,
  34. oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef),
  35. parentSuspense,
  36. vnode,
  37. isUnmount,
  38. ),
  39. )
  40. return
  41. }
  42. if (isAsyncWrapper(vnode) && !isUnmount) {
  43. // #4999 if an async component already resolved and cached by KeepAlive,
  44. // we need to set the ref to inner component
  45. if (
  46. vnode.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE &&
  47. (vnode.type as ComponentOptions).__asyncResolved &&
  48. vnode.component!.subTree.component
  49. ) {
  50. setRef(rawRef, oldRawRef, parentSuspense, vnode.component!.subTree)
  51. }
  52. // otherwise, nothing needs to be done because the template ref
  53. // is forwarded to inner component
  54. return
  55. }
  56. const refValue =
  57. vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
  58. ? getComponentPublicInstance(vnode.component!)
  59. : vnode.el
  60. const value = isUnmount ? null : refValue
  61. const { i: owner, r: ref } = rawRef
  62. if (__DEV__ && !owner) {
  63. warn(
  64. `Missing ref owner context. ref cannot be used on hoisted vnodes. ` +
  65. `A vnode with ref must be created inside the render function.`,
  66. )
  67. return
  68. }
  69. const oldRef = oldRawRef && (oldRawRef as VNodeNormalizedRefAtom).r
  70. const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
  71. const setupState = owner.setupState
  72. const rawSetupState = toRaw(setupState)
  73. const canSetSetupRef =
  74. setupState === EMPTY_OBJ
  75. ? () => false
  76. : (key: string) => {
  77. if (__DEV__) {
  78. if (hasOwn(rawSetupState, key) && !isRef(rawSetupState[key])) {
  79. warn(
  80. `Template ref "${key}" used on a non-ref value. ` +
  81. `It will not work in the production build.`,
  82. )
  83. }
  84. if (knownTemplateRefs.has(rawSetupState[key] as any)) {
  85. return false
  86. }
  87. }
  88. return hasOwn(rawSetupState, key)
  89. }
  90. // dynamic ref changed. unset old ref
  91. if (oldRef != null && oldRef !== ref) {
  92. if (isString(oldRef)) {
  93. refs[oldRef] = null
  94. if (canSetSetupRef(oldRef)) {
  95. setupState[oldRef] = null
  96. }
  97. } else if (isRef(oldRef)) {
  98. oldRef.value = null
  99. }
  100. }
  101. if (isFunction(ref)) {
  102. callWithErrorHandling(ref, owner, ErrorCodes.FUNCTION_REF, [value, refs])
  103. } else {
  104. const _isString = isString(ref)
  105. const _isRef = isRef(ref)
  106. if (_isString || _isRef) {
  107. const doSet = () => {
  108. if (rawRef.f) {
  109. const existing = _isString
  110. ? canSetSetupRef(ref)
  111. ? setupState[ref]
  112. : refs[ref]
  113. : ref.value
  114. if (isUnmount) {
  115. isArray(existing) && remove(existing, refValue)
  116. } else {
  117. if (!isArray(existing)) {
  118. if (_isString) {
  119. refs[ref] = [refValue]
  120. if (canSetSetupRef(ref)) {
  121. setupState[ref] = refs[ref]
  122. }
  123. } else {
  124. ref.value = [refValue]
  125. if (rawRef.k) refs[rawRef.k] = ref.value
  126. }
  127. } else if (!existing.includes(refValue)) {
  128. existing.push(refValue)
  129. }
  130. }
  131. } else if (_isString) {
  132. refs[ref] = value
  133. if (canSetSetupRef(ref)) {
  134. setupState[ref] = value
  135. }
  136. } else if (_isRef) {
  137. ref.value = value
  138. if (rawRef.k) refs[rawRef.k] = value
  139. } else if (__DEV__) {
  140. warn('Invalid template ref type:', ref, `(${typeof ref})`)
  141. }
  142. }
  143. if (value) {
  144. // #1789: for non-null values, set them after render
  145. // null values means this is unmount and it should not overwrite another
  146. // ref with the same key
  147. ;(doSet as SchedulerJob).id = -1
  148. queuePostRenderEffect(doSet, parentSuspense)
  149. } else {
  150. doSet()
  151. }
  152. } else if (__DEV__) {
  153. warn('Invalid template ref type:', ref, `(${typeof ref})`)
  154. }
  155. }
  156. }