import { SuspenseBoundary } from './components/Suspense' import { VNode, VNodeNormalizedRef, VNodeNormalizedRefAtom } from './vnode' import { EMPTY_OBJ, hasOwn, isArray, isFunction, isString, remove, ShapeFlags } from '@vue/shared' import { isAsyncWrapper } from './apiAsyncComponent' import { getExposeProxy } from './component' import { warn } from './warning' import { isRef } from '@vue/reactivity' import { callWithErrorHandling, ErrorCodes } from './errorHandling' import { SchedulerJob } from './scheduler' import { queuePostRenderEffect } from './renderer' /** * Function for handling a template ref */ export function setRef( rawRef: VNodeNormalizedRef, oldRawRef: VNodeNormalizedRef | null, parentSuspense: SuspenseBoundary | null, vnode: VNode, isUnmount = false ) { if (isArray(rawRef)) { rawRef.forEach((r, i) => setRef( r, oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef), parentSuspense, vnode, isUnmount ) ) return } if (isAsyncWrapper(vnode) && !isUnmount) { // when mounting async components, nothing needs to be done, // because the template ref is forwarded to inner component return } const refValue = vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT ? getExposeProxy(vnode.component!) || vnode.component!.proxy : 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 // dynamic ref changed. unset old ref if (oldRef != null && oldRef !== ref) { if (isString(oldRef)) { refs[oldRef] = null if (hasOwn(setupState, oldRef)) { setupState[oldRef] = null } } else if (isRef(oldRef)) { oldRef.value = 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 ? refs[ref] : ref.value if (isUnmount) { isArray(existing) && remove(existing, refValue) } else { if (!isArray(existing)) { if (_isString) { refs[ref] = [refValue] if (hasOwn(setupState, ref)) { setupState[ref] = refs[ref] } } else { ref.value = [refValue] if (rawRef.k) refs[rawRef.k] = ref.value } } else if (!existing.includes(refValue)) { existing.push(refValue) } } } else if (_isString) { refs[ref] = value if (hasOwn(setupState, ref)) { setupState[ref] = value } } else if (isRef(ref)) { 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 ;(doSet as SchedulerJob).id = -1 queuePostRenderEffect(doSet, parentSuspense) } else { doSet() } } else if (__DEV__) { warn('Invalid template ref type:', ref, `(${typeof ref})`) } } }