Skip to content

Commit 3ba70e4

Browse files
committed
feat: useTemplateRef()
1 parent 0ae7316 commit 3ba70e4

File tree

4 files changed

+105
-0
lines changed

4 files changed

+105
-0
lines changed

packages/dts-test/ref.test-d.ts

+8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
toRefs,
1818
toValue,
1919
unref,
20+
useTemplateRef,
2021
} from 'vue'
2122
import { type IsAny, type IsUnion, describe, expectType } from './utils'
2223

@@ -456,3 +457,10 @@ describe('toRef <-> toValue', () => {
456457
// unref
457458
declare const text: ShallowRef<string> | ComputedRef<string> | MaybeRef<string>
458459
expectType<string>(unref(text))
460+
461+
// useTemplateRef
462+
const tRef = useTemplateRef('foo')
463+
expectType<Readonly<ShallowRef<unknown>>>(tRef)
464+
465+
const tRef2 = useTemplateRef<HTMLElement>('bar')
466+
expectType<Readonly<ShallowRef<HTMLElement | null>>>(tRef2)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {
2+
h,
3+
nextTick,
4+
nodeOps,
5+
ref,
6+
render,
7+
useTemplateRef,
8+
} from '@vue/runtime-test'
9+
10+
describe('useTemplateRef', () => {
11+
test('should work', () => {
12+
let tRef
13+
const key = 'refKey'
14+
const Comp = {
15+
setup() {
16+
tRef = useTemplateRef(key)
17+
},
18+
render() {
19+
return h('div', { ref: key })
20+
},
21+
}
22+
const root = nodeOps.createElement('div')
23+
render(h(Comp), root)
24+
expect(tRef!.value).toBe(root.children[0])
25+
})
26+
27+
test('should be readonly', () => {
28+
let tRef
29+
const key = 'refKey'
30+
const Comp = {
31+
setup() {
32+
tRef = useTemplateRef(key)
33+
},
34+
render() {
35+
return h('div', { ref: key })
36+
},
37+
}
38+
const root = nodeOps.createElement('div')
39+
render(h(Comp), root)
40+
41+
// @ts-expect-error
42+
tRef.value = 123
43+
44+
expect(tRef!.value).toBe(root.children[0])
45+
expect('target is readonly').toHaveBeenWarned()
46+
})
47+
48+
test('should be updated for ref of dynamic strings', async () => {
49+
let t1, t2
50+
const key = ref('t1')
51+
const Comp = {
52+
setup() {
53+
t1 = useTemplateRef<HTMLAnchorElement>('t1')
54+
t2 = useTemplateRef('t2')
55+
},
56+
render() {
57+
return h('div', { ref: key.value })
58+
},
59+
}
60+
const root = nodeOps.createElement('div')
61+
render(h(Comp), root)
62+
63+
expect(t1!.value).toBe(root.children[0])
64+
expect(t2!.value).toBe(null)
65+
66+
key.value = 't2'
67+
await nextTick()
68+
expect(t2!.value).toBe(root.children[0])
69+
expect(t1!.value).toBe(null)
70+
})
71+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { type ShallowRef, readonly, shallowRef } from '@vue/reactivity'
2+
import { getCurrentInstance } from './component'
3+
import { warn } from './warning'
4+
import { EMPTY_OBJ } from '@vue/shared'
5+
6+
export function useTemplateRef<T = unknown>(
7+
key: string,
8+
): Readonly<ShallowRef<T | null>> {
9+
const i = getCurrentInstance()
10+
const r = shallowRef(null)
11+
if (i) {
12+
const refs = i.refs === EMPTY_OBJ ? (i.refs = {}) : i.refs
13+
Object.defineProperty(refs, key, {
14+
enumerable: true,
15+
get: () => r.value,
16+
set: val => (r.value = val),
17+
})
18+
} else if (__DEV__) {
19+
warn(
20+
`useTemplateRef() is called when there is no active component ` +
21+
`instance to be associated with.`,
22+
)
23+
}
24+
return (__DEV__ ? readonly(r) : r) as any
25+
}

packages/runtime-core/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export { defineComponent } from './apiDefineComponent'
6262
export { defineAsyncComponent } from './apiAsyncComponent'
6363
export { useAttrs, useSlots } from './apiSetupHelpers'
6464
export { useModel } from './helpers/useModel'
65+
export { useTemplateRef } from './apiTemplateRef'
6566

6667
// <script setup> API ----------------------------------------------------------
6768

0 commit comments

Comments
 (0)