diff --git a/packages/runtime-core/__tests__/helpers/useTemplateRef.spec.ts b/packages/runtime-core/__tests__/helpers/useTemplateRef.spec.ts index 10de6f48353..adc8ed66c77 100644 --- a/packages/runtime-core/__tests__/helpers/useTemplateRef.spec.ts +++ b/packages/runtime-core/__tests__/helpers/useTemplateRef.spec.ts @@ -1,4 +1,5 @@ import { + type ShallowRef, h, nextTick, nodeOps, @@ -84,12 +85,12 @@ describe('useTemplateRef', () => { }) // #11795 - test('should work when variable name is same as key', () => { - let tRef + test('should not attempt to set when variable name is same as key', () => { + let tRef: ShallowRef const key = 'refKey' const Comp = { setup() { - tRef = useTemplateRef(key) + tRef = useTemplateRef('_') return { [key]: tRef, } @@ -102,5 +103,26 @@ describe('useTemplateRef', () => { render(h(Comp), root) expect('target is readonly').not.toHaveBeenWarned() + expect(tRef!.value).toBe(null) + }) + + test('should work when used as direct ref value (compiled in prod mode)', () => { + __DEV__ = false + try { + let foo: ShallowRef + const Comp = { + setup() { + foo = useTemplateRef('foo') + return () => h('div', { ref: foo }) + }, + } + const root = nodeOps.createElement('div') + render(h(Comp), root) + + expect('target is readonly').not.toHaveBeenWarned() + expect(foo!.value).toBe(root.children[0]) + } finally { + __DEV__ = true + } }) }) diff --git a/packages/runtime-core/src/helpers/useTemplateRef.ts b/packages/runtime-core/src/helpers/useTemplateRef.ts index 58c109a9246..4cb10ea8139 100644 --- a/packages/runtime-core/src/helpers/useTemplateRef.ts +++ b/packages/runtime-core/src/helpers/useTemplateRef.ts @@ -3,6 +3,8 @@ import { getCurrentInstance } from '../component' import { warn } from '../warning' import { EMPTY_OBJ } from '@vue/shared' +export const knownTemplateRefs: WeakSet = new WeakSet() + export function useTemplateRef( key: Keys, ): Readonly> { @@ -10,7 +12,6 @@ export function useTemplateRef( const r = shallowRef(null) if (i) { const refs = i.refs === EMPTY_OBJ ? (i.refs = {}) : i.refs - let desc: PropertyDescriptor | undefined if ( __DEV__ && @@ -31,5 +32,9 @@ export function useTemplateRef( `instance to be associated with.`, ) } - return (__DEV__ ? readonly(r) : r) as any + const ret = __DEV__ ? readonly(r) : r + if (__DEV__) { + knownTemplateRefs.add(ret) + } + return ret } diff --git a/packages/runtime-core/src/rendererTemplateRef.ts b/packages/runtime-core/src/rendererTemplateRef.ts index c7b15fe4022..1ffe3035794 100644 --- a/packages/runtime-core/src/rendererTemplateRef.ts +++ b/packages/runtime-core/src/rendererTemplateRef.ts @@ -11,11 +11,12 @@ import { } from '@vue/shared' import { isAsyncWrapper } from './apiAsyncComponent' import { warn } from './warning' -import { isRef } from '@vue/reactivity' +import { isRef, toRaw } from '@vue/reactivity' import { ErrorCodes, callWithErrorHandling } from './errorHandling' import type { SchedulerJob } from './scheduler' import { queuePostRenderEffect } from './renderer' import { getComponentPublicInstance } from './component' +import { knownTemplateRefs } from './helpers/useTemplateRef' /** * Function for handling a template ref @@ -63,12 +64,16 @@ export function setRef( 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 ? () => false - : (key: string) => - hasOwn(setupState, key) && - !(Object.getOwnPropertyDescriptor(refs, key) || EMPTY_OBJ).get + : (key: string) => { + if (__DEV__ && knownTemplateRefs.has(rawSetupState[key] as any)) { + return false + } + return hasOwn(rawSetupState, key) + } // dynamic ref changed. unset old ref if (oldRef != null && oldRef !== ref) {