rendererTemplateRef.ts 6.3 KB

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