vue3-yuanma/packages/runtime-core/src/rendererTemplateRef.ts

131 lines
3.8 KiB
TypeScript

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})`)
}
}
}