refactor: move template ref setter into dedicated file

This commit is contained in:
Evan You 2021-12-11 09:05:51 +08:00
parent 41c18effea
commit 1928c9b537
6 changed files with 157 additions and 143 deletions

View File

@ -8,7 +8,13 @@ import {
getComponentName,
ComponentOptions
} from '../component'
import { VNode, cloneVNode, isVNode, VNodeProps } from '../vnode'
import {
VNode,
cloneVNode,
isVNode,
VNodeProps,
invokeVNodeHook
} from '../vnode'
import { warn } from '../warning'
import {
onBeforeUnmount,
@ -30,8 +36,7 @@ import {
queuePostRenderEffect,
MoveType,
RendererElement,
RendererNode,
invokeVNodeHook
RendererNode
} from '../renderer'
import { setTransitionHooks } from './BaseTransition'
import { ComponentRenderContext } from '../componentPublicInstance'

View File

@ -7,14 +7,16 @@ import {
Fragment,
VNodeHook,
createVNode,
createTextVNode
createTextVNode,
invokeVNodeHook
} from './vnode'
import { flushPostFlushCbs } from './scheduler'
import { ComponentInternalInstance } from './component'
import { invokeDirectiveHook } from './directives'
import { warn } from './warning'
import { PatchFlags, ShapeFlags, isReservedProp, isOn } from '@vue/shared'
import { RendererInternals, invokeVNodeHook, setRef } from './renderer'
import { RendererInternals } from './renderer'
import { setRef } from './rendererTemplateRef'
import {
SuspenseImpl,
SuspenseBoundary,

View File

@ -9,17 +9,15 @@ import {
createVNode,
isSameVNodeType,
Static,
VNodeNormalizedRef,
VNodeHook,
VNodeNormalizedRefAtom,
VNodeProps
VNodeProps,
invokeVNodeHook
} from './vnode'
import {
ComponentInternalInstance,
ComponentOptions,
createComponentInstance,
Data,
getExposeProxy,
setupComponent
} from './component'
import {
@ -29,19 +27,15 @@ import {
updateHOCHostEl
} from './componentRenderUtils'
import {
isString,
EMPTY_OBJ,
EMPTY_ARR,
isReservedProp,
isFunction,
PatchFlags,
ShapeFlags,
NOOP,
hasOwn,
invokeArrayFns,
isArray,
getGlobalThis,
remove
getGlobalThis
} from '@vue/shared'
import {
queueJob,
@ -51,16 +45,12 @@ import {
flushPreFlushCbs,
SchedulerJob
} from './scheduler'
import {
isRef,
pauseTracking,
resetTracking,
ReactiveEffect
} from '@vue/reactivity'
import { pauseTracking, resetTracking, ReactiveEffect } from '@vue/reactivity'
import { updateProps } from './componentProps'
import { updateSlots } from './componentSlots'
import { pushWarningContext, popWarningContext, warn } from './warning'
import { createAppAPI, CreateAppFunction } from './apiCreateApp'
import { setRef } from './rendererTemplateRef'
import {
SuspenseBoundary,
queueEffectWithSuspense,
@ -69,11 +59,6 @@ import {
import { TeleportImpl, TeleportVNode } from './components/Teleport'
import { isKeepAlive, KeepAliveContext } from './components/KeepAlive'
import { registerHMR, unregisterHMR, isHmrUpdating } from './hmr'
import {
ErrorCodes,
callWithErrorHandling,
callWithAsyncErrorHandling
} from './errorHandling'
import { createHydrationFunctions, RootHydrateFunction } from './hydration'
import { invokeDirectiveHook } from './directives'
import { startMeasure, endMeasure } from './profiling'
@ -2351,124 +2336,6 @@ function baseCreateRenderer(
}
}
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]
} 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})`)
}
}
}
export function invokeVNodeHook(
hook: VNodeHook,
instance: ComponentInternalInstance | null,
vnode: VNode,
prevVNode: VNode | null = null
) {
callWithAsyncErrorHandling(hook, instance, ErrorCodes.VNODE_HOOK, [
vnode,
prevVNode
])
}
function toggleRecurse(
{ effect, update }: ComponentInternalInstance,
allowed: boolean

View File

@ -0,0 +1,127 @@
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]
} 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})`)
}
}
}

View File

@ -42,6 +42,7 @@ import { hmrDirtyComponents } from './hmr'
import { convertLegacyComponent } from './compat/component'
import { convertLegacyVModelProps } from './compat/componentVModel'
import { defineLegacyVNodeProperties } from './compat/renderFn'
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
export const Fragment = Symbol(__DEV__ ? 'Fragment' : undefined) as any as {
__isFragment: true
@ -811,3 +812,15 @@ export function mergeProps(...args: (Data & VNodeProps)[]) {
}
return ret
}
export function invokeVNodeHook(
hook: VNodeHook,
instance: ComponentInternalInstance | null,
vnode: VNode,
prevVNode: VNode | null = null
) {
callWithAsyncErrorHandling(hook, instance, ErrorCodes.VNODE_HOOK, [
vnode,
prevVNode
])
}