Skip to content

Commit e56997f

Browse files
authored
fix(runtime-vapor): fix readonly warning when useTemplateRef has same variable name as template ref (#13672)
close #13665 align to bc63df0
1 parent def21b6 commit e56997f

File tree

4 files changed

+82
-26
lines changed

4 files changed

+82
-26
lines changed

packages/runtime-core/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,3 +562,7 @@ export { initFeatureFlags } from './featureFlags'
562562
* @internal
563563
*/
564564
export { createInternalObject } from './internalObject'
565+
/**
566+
* @internal
567+
*/
568+
export { createCanSetSetupRefChecker } from './rendererTemplateRef'

packages/runtime-core/src/rendererTemplateRef.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import { warn } from './warning'
1414
import { isRef, toRaw } from '@vue/reactivity'
1515
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
1616
import { queuePostRenderEffect } from './renderer'
17-
import { type ComponentOptions, getComponentPublicInstance } from './component'
17+
import {
18+
type ComponentOptions,
19+
type Data,
20+
getComponentPublicInstance,
21+
} from './component'
1822
import { knownTemplateRefs } from './helpers/useTemplateRef'
1923

2024
/**
@@ -73,25 +77,7 @@ export function setRef(
7377
const oldRef = oldRawRef && (oldRawRef as VNodeNormalizedRefAtom).r
7478
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
7579
const setupState = owner.setupState
76-
const rawSetupState = toRaw(setupState)
77-
const canSetSetupRef =
78-
setupState === EMPTY_OBJ
79-
? () => false
80-
: (key: string) => {
81-
if (__DEV__) {
82-
if (hasOwn(rawSetupState, key) && !isRef(rawSetupState[key])) {
83-
warn(
84-
`Template ref "${key}" used on a non-ref value. ` +
85-
`It will not work in the production build.`,
86-
)
87-
}
88-
89-
if (knownTemplateRefs.has(rawSetupState[key] as any)) {
90-
return false
91-
}
92-
}
93-
return hasOwn(rawSetupState, key)
94-
}
80+
const canSetSetupRef = createCanSetSetupRefChecker(setupState)
9581

9682
// dynamic ref changed. unset old ref
9783
if (oldRef != null && oldRef !== ref) {
@@ -161,3 +147,26 @@ export function setRef(
161147
}
162148
}
163149
}
150+
151+
export function createCanSetSetupRefChecker(
152+
setupState: Data,
153+
): (key: string) => boolean {
154+
const rawSetupState = toRaw(setupState)
155+
return setupState === EMPTY_OBJ
156+
? () => false
157+
: (key: string) => {
158+
if (__DEV__) {
159+
if (hasOwn(rawSetupState, key) && !isRef(rawSetupState[key])) {
160+
warn(
161+
`Template ref "${key}" used on a non-ref value. ` +
162+
`It will not work in the production build.`,
163+
)
164+
}
165+
166+
if (knownTemplateRefs.has(rawSetupState[key] as any)) {
167+
return false
168+
}
169+
}
170+
return hasOwn(rawSetupState, key)
171+
}
172+
}

packages/runtime-vapor/__tests__/dom/templateRef.spec.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
nextTick,
2020
reactive,
2121
ref,
22+
shallowRef,
2223
useTemplateRef,
2324
watchEffect,
2425
} from '@vue/runtime-dom'
@@ -208,8 +209,8 @@ describe('api: template ref', () => {
208209
const { render } = define({
209210
setup() {
210211
return {
211-
foo: fooEl,
212-
bar: barEl,
212+
foo: shallowRef(fooEl),
213+
bar: shallowRef(barEl),
213214
}
214215
},
215216
render() {
@@ -251,6 +252,7 @@ describe('api: template ref', () => {
251252
})
252253
const { host } = render()
253254
expect(state.refKey).toBe(host.children[0])
255+
expect('Template ref "refKey" used on a non-ref value').toHaveBeenWarned()
254256
})
255257

256258
test('multiple root refs', () => {
@@ -713,6 +715,45 @@ describe('api: template ref', () => {
713715
expect(html()).toBe('<div>changed</div><!--dynamic-component-->')
714716
})
715717

718+
test('should not attempt to set when variable name is same as key', () => {
719+
let tRef: ShallowRef
720+
const key = 'refKey'
721+
define({
722+
setup() {
723+
tRef = useTemplateRef('_')
724+
return {
725+
[key]: tRef,
726+
}
727+
},
728+
render() {
729+
const n0 = template('<div></div>')() as Element
730+
createTemplateRefSetter()(n0, key)
731+
return n0
732+
},
733+
}).render()
734+
expect('target is readonly').not.toHaveBeenWarned()
735+
expect(tRef!.value).toBe(null)
736+
})
737+
738+
test('should work when used as direct ref value (compiled in prod mode)', () => {
739+
__DEV__ = false
740+
try {
741+
let foo: ShallowRef
742+
const { host } = define({
743+
setup() {
744+
foo = useTemplateRef('foo')
745+
const n0 = template('<div></div>')() as Element
746+
createTemplateRefSetter()(n0, foo)
747+
return n0
748+
},
749+
}).render()
750+
expect('target is readonly').not.toHaveBeenWarned()
751+
expect(foo!.value).toBe(host.children[0])
752+
} finally {
753+
__DEV__ = true
754+
}
755+
})
756+
716757
// TODO: can not reproduce in Vapor
717758
// // #2078
718759
// test('handling multiple merged refs', async () => {

packages/runtime-vapor/src/apiTemplateRef.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
ErrorCodes,
1010
type SchedulerJob,
1111
callWithErrorHandling,
12+
createCanSetSetupRefChecker,
1213
queuePostFlushCb,
1314
warn,
1415
} from '@vue/runtime-dom'
@@ -55,6 +56,7 @@ export function setRef(
5556
const refs =
5657
instance.refs === EMPTY_OBJ ? (instance.refs = {}) : instance.refs
5758

59+
const canSetSetupRef = createCanSetSetupRefChecker(setupState)
5860
// dynamic ref changed. unset old ref
5961
if (oldRef != null && oldRef !== ref) {
6062
if (isString(oldRef)) {
@@ -87,7 +89,7 @@ export function setRef(
8789
const doSet: SchedulerJob = () => {
8890
if (refFor) {
8991
existing = _isString
90-
? __DEV__ && hasOwn(setupState, ref)
92+
? __DEV__ && canSetSetupRef(ref)
9193
? setupState[ref]
9294
: refs[ref]
9395
: ref.value
@@ -96,7 +98,7 @@ export function setRef(
9698
existing = [refValue]
9799
if (_isString) {
98100
refs[ref] = existing
99-
if (__DEV__ && hasOwn(setupState, ref)) {
101+
if (__DEV__ && canSetSetupRef(ref)) {
100102
setupState[ref] = refs[ref]
101103
// if setupState[ref] is a reactivity ref,
102104
// the existing will also become reactivity too
@@ -111,7 +113,7 @@ export function setRef(
111113
}
112114
} else if (_isString) {
113115
refs[ref] = refValue
114-
if (__DEV__ && hasOwn(setupState, ref)) {
116+
if (__DEV__ && canSetSetupRef(ref)) {
115117
setupState[ref] = refValue
116118
}
117119
} else if (_isRef) {
@@ -129,7 +131,7 @@ export function setRef(
129131
remove(existing, refValue)
130132
} else if (_isString) {
131133
refs[ref] = null
132-
if (__DEV__ && hasOwn(setupState, ref)) {
134+
if (__DEV__ && canSetSetupRef(ref)) {
133135
setupState[ref] = null
134136
}
135137
} else if (_isRef) {

0 commit comments

Comments
 (0)