| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- import type { SuspenseBoundary } from './components/Suspense'
- import type {
- VNode,
- VNodeNormalizedRef,
- VNodeNormalizedRefAtom,
- VNodeRef,
- } from './vnode'
- import {
- EMPTY_OBJ,
- NO,
- ShapeFlags,
- hasOwn,
- isArray,
- isFunction,
- isString,
- remove,
- } from '@vue/shared'
- import { isAsyncWrapper } from './apiAsyncComponent'
- import { warn } from './warning'
- import { isRef, toRaw } from '@vue/reactivity'
- import { ErrorCodes, callWithErrorHandling } from './errorHandling'
- import { type SchedulerJob, SchedulerJobFlags } from './scheduler'
- import { queuePostRenderEffect } from './renderer'
- import { type ComponentOptions, getComponentPublicInstance } from './component'
- import { isTemplateRefKey, knownTemplateRefs } from './helpers/useTemplateRef'
- const pendingSetRefMap = new WeakMap<VNodeNormalizedRef, SchedulerJob>()
- /**
- * Function for handling a template ref
- */
- export function setRef(
- rawRef: VNodeNormalizedRef,
- oldRawRef: VNodeNormalizedRef | null,
- parentSuspense: SuspenseBoundary | null,
- vnode: VNode,
- isUnmount = false,
- ): void {
- if (isArray(rawRef)) {
- rawRef.forEach((r, i) =>
- setRef(
- r,
- oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef),
- parentSuspense,
- vnode,
- isUnmount,
- ),
- )
- return
- }
- if (isAsyncWrapper(vnode) && !isUnmount) {
- // #4999 if an async component already resolved and cached by KeepAlive,
- // we need to set the ref to inner component
- if (
- vnode.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE &&
- (vnode.type as ComponentOptions).__asyncResolved &&
- vnode.component!.subTree.component
- ) {
- setRef(rawRef, oldRawRef, parentSuspense, vnode.component!.subTree)
- }
- // otherwise, nothing needs to be done because the template ref
- // is forwarded to inner component
- return
- }
- const refValue =
- vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
- ? getComponentPublicInstance(vnode.component!)
- : vnode.el
- const value = isUnmount ? null : refValue
- const { i: owner, r: ref } = rawRef
- if (__DEV__ && !owner) {
- warn(
- `Missing ref owner context. ref cannot be used on hoisted vnodes. ` +
- `A vnode with ref must be created inside the render function.`,
- )
- return
- }
- const oldRef = oldRawRef && (oldRawRef as VNodeNormalizedRefAtom).r
- const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
- const setupState = owner.setupState
- const rawSetupState = toRaw(setupState)
- const canSetSetupRef =
- setupState === EMPTY_OBJ
- ? NO
- : (key: string) => {
- if (__DEV__) {
- if (hasOwn(rawSetupState, key) && !isRef(rawSetupState[key])) {
- warn(
- `Template ref "${key}" used on a non-ref value. ` +
- `It will not work in the production build.`,
- )
- }
- if (knownTemplateRefs.has(rawSetupState[key] as any)) {
- return false
- }
- }
- // skip setting up ref if the key is from useTemplateRef
- if (isTemplateRefKey(refs, key)) {
- return false
- }
- return hasOwn(rawSetupState, key)
- }
- const canSetRef = (ref: VNodeRef, key?: string) => {
- if (__DEV__ && knownTemplateRefs.has(ref as any)) {
- return false
- }
- if (key && isTemplateRefKey(refs, key)) {
- return false
- }
- return true
- }
- // dynamic ref changed. unset old ref
- if (oldRef != null && oldRef !== ref) {
- invalidatePendingSetRef(oldRawRef!)
- if (isString(oldRef)) {
- refs[oldRef] = null
- if (canSetSetupRef(oldRef)) {
- setupState[oldRef] = null
- }
- } else if (isRef(oldRef)) {
- // this type assertion is valid since `oldRef` has already been asserted to be non-null
- const oldRawRefAtom = oldRawRef as VNodeNormalizedRefAtom
- if (canSetRef(oldRef, oldRawRefAtom.k)) {
- oldRef.value = null
- }
- if (oldRawRefAtom.k) refs[oldRawRefAtom.k] = null
- }
- }
- if (isFunction(ref)) {
- callWithErrorHandling(ref, owner, ErrorCodes.FUNCTION_REF, [value, refs])
- } else {
- const _isString = isString(ref)
- const _isRef = isRef(ref)
- if (_isString || _isRef) {
- const doSet = () => {
- if (rawRef.f) {
- const existing = _isString
- ? canSetSetupRef(ref)
- ? setupState[ref]
- : refs[ref]
- : canSetRef(ref) || !rawRef.k
- ? ref.value
- : refs[rawRef.k]
- if (isUnmount) {
- isArray(existing) && remove(existing, refValue)
- } else {
- if (!isArray(existing)) {
- if (_isString) {
- refs[ref] = [refValue]
- if (canSetSetupRef(ref)) {
- setupState[ref] = refs[ref]
- }
- } else {
- const newVal = [refValue]
- if (canSetRef(ref, rawRef.k)) {
- ref.value = newVal
- }
- if (rawRef.k) refs[rawRef.k] = newVal
- }
- } else if (!existing.includes(refValue)) {
- existing.push(refValue)
- }
- }
- } else if (_isString) {
- refs[ref] = value
- if (canSetSetupRef(ref)) {
- setupState[ref] = value
- }
- } else if (_isRef) {
- if (canSetRef(ref, rawRef.k)) {
- ref.value = value
- }
- if (rawRef.k) refs[rawRef.k] = value
- } else if (__DEV__) {
- warn('Invalid template ref type:', ref, `(${typeof ref})`)
- }
- }
- if (value) {
- // #1789: for non-null values, set them after render
- // null values means this is unmount and it should not overwrite another
- // ref with the same key
- const job: SchedulerJob = () => {
- doSet()
- pendingSetRefMap.delete(rawRef)
- }
- job.id = -1
- pendingSetRefMap.set(rawRef, job)
- queuePostRenderEffect(job, parentSuspense)
- } else {
- invalidatePendingSetRef(rawRef)
- doSet()
- }
- } else if (__DEV__) {
- warn('Invalid template ref type:', ref, `(${typeof ref})`)
- }
- }
- }
- function invalidatePendingSetRef(rawRef: VNodeNormalizedRef) {
- const pendingSetRef = pendingSetRefMap.get(rawRef)
- if (pendingSetRef) {
- pendingSetRef.flags! |= SchedulerJobFlags.DISPOSED
- pendingSetRefMap.delete(rawRef)
- }
- }
|