cee1eafb4d
close #5116 close #5447 close #5525
131 lines
3.8 KiB
TypeScript
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})`)
|
|
}
|
|
}
|
|
}
|