rendererTemplateRef.ts 6.1 KB

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