Skip to content

Commit b8e63f7

Browse files
committed
fix(runtime-core): avoid setting direct ref of useTemplateRef in dev
1 parent cdffaf6 commit b8e63f7

File tree

3 files changed

+157
-6
lines changed

3 files changed

+157
-6
lines changed

packages/runtime-core/__tests__/helpers/useTemplateRef.spec.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,134 @@ describe('useTemplateRef', () => {
106106
expect(tRef!.value).toBe(null)
107107
})
108108

109+
test('should work when used with direct ref value with ref_key', () => {
110+
let tRef: ShallowRef
111+
const key = 'refKey'
112+
const Comp = {
113+
setup() {
114+
tRef = useTemplateRef(key)
115+
return () => h('div', { ref: tRef, ref_key: key })
116+
},
117+
}
118+
const root = nodeOps.createElement('div')
119+
render(h(Comp), root)
120+
121+
expect('target is readonly').not.toHaveBeenWarned()
122+
expect(tRef!.value).toBe(root.children[0])
123+
})
124+
125+
test('should work when used with direct ref value with ref_key and ref_for', () => {
126+
let tRef: ShallowRef
127+
const key = 'refKey'
128+
const Comp = {
129+
setup() {
130+
tRef = useTemplateRef(key)
131+
},
132+
render() {
133+
return h(
134+
'div',
135+
[1, 2, 3].map(x =>
136+
h('span', { ref: tRef, ref_key: key, ref_for: true }, x.toString()),
137+
),
138+
)
139+
},
140+
}
141+
const root = nodeOps.createElement('div')
142+
render(h(Comp), root)
143+
144+
expect('target is readonly').not.toHaveBeenWarned()
145+
expect(tRef!.value).toHaveLength(3)
146+
})
147+
148+
test('should work when used with direct ref value with ref_key and dynamic value', async () => {
149+
const refMode = ref('h1-ref')
150+
151+
let tRef: ShallowRef
152+
const key = 'refKey'
153+
154+
const Comp = {
155+
setup() {
156+
tRef = useTemplateRef(key)
157+
},
158+
render() {
159+
switch (refMode.value) {
160+
case 'h1-ref':
161+
return h('h1', { ref: tRef, ref_key: key })
162+
case 'h2-ref':
163+
return h('h2', { ref: tRef, ref_key: key })
164+
case 'no-ref':
165+
return h('span')
166+
case 'nothing':
167+
return null
168+
}
169+
},
170+
}
171+
172+
const root = nodeOps.createElement('div')
173+
render(h(Comp), root)
174+
175+
expect(tRef!.value.tag).toBe('h1')
176+
177+
refMode.value = 'h2-ref'
178+
await nextTick()
179+
expect(tRef!.value.tag).toBe('h2')
180+
181+
refMode.value = 'no-ref'
182+
await nextTick()
183+
expect(tRef!.value).toBeNull()
184+
185+
refMode.value = 'nothing'
186+
await nextTick()
187+
expect(tRef!.value).toBeNull()
188+
189+
expect('target is readonly').not.toHaveBeenWarned()
190+
})
191+
192+
test('should work when used with dynamic direct refs and ref_keys', async () => {
193+
const refKey = ref('foo')
194+
195+
let tRefs: Record<string, ShallowRef>
196+
197+
const Comp = {
198+
setup() {
199+
tRefs = {
200+
foo: useTemplateRef('foo'),
201+
bar: useTemplateRef('bar'),
202+
}
203+
},
204+
render() {
205+
return h('div', { ref: tRefs[refKey.value], ref_key: refKey.value })
206+
},
207+
}
208+
209+
const root = nodeOps.createElement('div')
210+
render(h(Comp), root)
211+
212+
expect(tRefs!['foo'].value).toBe(root.children[0])
213+
expect(tRefs!['bar'].value).toBeNull()
214+
215+
refKey.value = 'bar'
216+
await nextTick()
217+
expect(tRefs!['foo'].value).toBeNull()
218+
expect(tRefs!['bar'].value).toBe(root.children[0])
219+
220+
expect('target is readonly').not.toHaveBeenWarned()
221+
})
222+
223+
test('should not work when used with direct ref value without ref_key (in dev mode)', () => {
224+
let tRef: ShallowRef
225+
const Comp = {
226+
setup() {
227+
tRef = useTemplateRef('refKey')
228+
return () => h('div', { ref: tRef })
229+
},
230+
}
231+
const root = nodeOps.createElement('div')
232+
render(h(Comp), root)
233+
234+
expect(tRef!.value).toBeNull()
235+
})
236+
109237
test('should work when used as direct ref value (compiled in prod mode)', () => {
110238
__DEV__ = false
111239
try {

packages/runtime-core/src/rendererTemplateRef.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import type { SuspenseBoundary } from './components/Suspense'
2-
import type { VNode, VNodeNormalizedRef, VNodeNormalizedRefAtom } from './vnode'
2+
import type {
3+
VNode,
4+
VNodeNormalizedRef,
5+
VNodeNormalizedRefAtom,
6+
VNodeRef,
7+
} from './vnode'
38
import {
49
EMPTY_OBJ,
510
ShapeFlags,
@@ -94,6 +99,10 @@ export function setRef(
9499
return hasOwn(rawSetupState, key)
95100
}
96101

102+
const canSetRef = (ref: VNodeRef) => {
103+
return !__DEV__ || !knownTemplateRefs.has(ref as any)
104+
}
105+
97106
// dynamic ref changed. unset old ref
98107
if (oldRef != null && oldRef !== ref) {
99108
if (isString(oldRef)) {
@@ -102,7 +111,13 @@ export function setRef(
102111
setupState[oldRef] = null
103112
}
104113
} else if (isRef(oldRef)) {
105-
oldRef.value = null
114+
if (canSetRef(oldRef)) {
115+
oldRef.value = null
116+
}
117+
118+
// this type assertion is valid since `oldRef` has already been asserted to be non-null
119+
const oldRawRefAtom = oldRawRef as VNodeNormalizedRefAtom
120+
if (oldRawRefAtom.k) refs[oldRawRefAtom.k] = null
106121
}
107122
}
108123

@@ -119,7 +134,9 @@ export function setRef(
119134
? canSetSetupRef(ref)
120135
? setupState[ref]
121136
: refs[ref]
122-
: ref.value
137+
: canSetRef(ref) || !rawRef.k
138+
? ref.value
139+
: refs[rawRef.k]
123140
if (isUnmount) {
124141
isArray(existing) && remove(existing, refValue)
125142
} else {
@@ -130,8 +147,11 @@ export function setRef(
130147
setupState[ref] = refs[ref]
131148
}
132149
} else {
133-
ref.value = [refValue]
134-
if (rawRef.k) refs[rawRef.k] = ref.value
150+
const newVal = [refValue]
151+
if (canSetRef(ref)) {
152+
ref.value = newVal
153+
}
154+
if (rawRef.k) refs[rawRef.k] = newVal
135155
}
136156
} else if (!existing.includes(refValue)) {
137157
existing.push(refValue)
@@ -143,7 +163,9 @@ export function setRef(
143163
setupState[ref] = value
144164
}
145165
} else if (_isRef) {
146-
ref.value = value
166+
if (canSetRef(ref)) {
167+
ref.value = value
168+
}
147169
if (rawRef.k) refs[rawRef.k] = value
148170
} else if (__DEV__) {
149171
warn('Invalid template ref type:', ref, `(${typeof ref})`)

packages/vue/__tests__/e2e/Transition.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3082,6 +3082,7 @@ describe('e2e: Transition', () => {
30823082

30833083
// enter
30843084
await classWhenTransitionStart()
3085+
await nextFrame()
30853086
await transitionFinish()
30863087

30873088
// leave

0 commit comments

Comments
 (0)