Skip to content

Commit 71cec81

Browse files
committed
3.5: useTemplateRef()
1 parent 9d52084 commit 71cec81

File tree

3 files changed

+176
-29
lines changed

3 files changed

+176
-29
lines changed

src/api/composition-api-helpers.md

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,45 @@
44

55
Returns the `attrs` object from the [Setup Context](/api/composition-api-setup#setup-context), which includes the [fallthrough attributes](/guide/components/attrs#fallthrough-attributes) of the current component. This is intended to be used in `<script setup>` where the setup context object is not available.
66

7+
- **Type**
8+
9+
```ts
10+
function useAttrs(): Record<string, unknown>
11+
```
12+
713
## useSlots() {#useslots}
814

915
Returns the `slots` object from the [Setup Context](/api/composition-api-setup#setup-context), which includes parent passed slots as callable functions that return Virtual DOM nodes. This is intended to be used in `<script setup>` where the setup context object is not available.
1016

1117
If using TypeScript, [`defineSlots()`](/api/sfc-script-setup#defineslots) should be preferred instead.
1218

19+
- **Type**
20+
21+
```ts
22+
function useSlots(): Record<string, (...args: any[]) => VNode[]>
23+
```
24+
1325
## useModel() <sup class="vt-badge" data-text="3.4+" /> {#usemodel}
1426

1527
This is the underlying helper that powers [`defineModel()`](/api/sfc-script-setup#definemodel). If using `<script setup>`, `defineModel()` should be preferred instead.
1628

1729
`useModel()` can be used in non-SFC components, e.g. when using raw `setup()` function. It expects the `props` object as the first argument, and the model name as the second argument. The optional third argument can be used to declare custom getter and setter for the resulting model ref. Note that unlike `defineModel()`, you are responsible for declaring the props and emits yourself.
1830

31+
- **Type**
32+
33+
```ts
34+
function useModel(
35+
props: Record<string, any>,
36+
key: string,
37+
options?: DefineModelOptions
38+
)
39+
40+
type DefineModelOptions<T = any> = {
41+
get?: (v: T) => any
42+
set?: (v: T) => any
43+
}
44+
```
45+
1946
- **Example**
2047

2148
```js
@@ -31,9 +58,46 @@ This is the underlying helper that powers [`defineModel()`](/api/sfc-script-setu
3158

3259
## useTemplateRef() <sup class="vt-badge" data-text="3.5+" /> {#usetemplateref}
3360

61+
Returns a shallow ref whose value will be synced with the template element or component with a matching ref attribute.
62+
63+
- **Type**
64+
65+
```ts
66+
function useTemplateRef<T>(key: string): Readonly<ShallowRef<T | null>>
67+
```
68+
69+
- **Example**
70+
71+
```vue
72+
<script setup>
73+
import { useTemplateRef, onMounted } from 'vue'
74+
75+
const inputRef = useTemplateRef('input')
76+
77+
onMounted(() => {
78+
inputRef.value.focus()
79+
})
80+
</script>
81+
82+
<template>
83+
<input ref="input" />
84+
</template>
85+
```
86+
87+
- **See also**
88+
- [Guide - Template Refs](/guide/essentials/template-refs)
89+
- [Guide - Typing Template Refs](/guide/typescript/composition-api#typing-template-refs) <sup class="vt-badge ts" />
90+
- [Guide - Typing Component Template Refs](/guide/typescript/composition-api#typing-component-template-refs) <sup class="vt-badge ts" />
91+
3492
## useId() <sup class="vt-badge" data-text="3.5+" /> {#useid}
3593

36-
`useId()` is an API that can be used to generate unique-per-application IDs.
94+
Used to generate unique-per-application IDs for accessibility attributes or form elements.
95+
96+
- **Type**
97+
98+
```ts
99+
function useId(): string
100+
```
37101

38102
- **Example**
39103

@@ -55,7 +119,7 @@ This is the underlying helper that powers [`defineModel()`](/api/sfc-script-setu
55119
- **Details**
56120

57121
IDs generated by `useId()` are unique-per-application. It can be used to generate IDs for form elements and accessibility attributes. Multiple calls in the same component will generate different IDs; multiple instances of the same component calling `useId()` will also have different IDs.
58-
122+
59123
IDs generated by `useId()` are also guaranteed to be stable across the server and client renders, so they can be used in SSR applications without leading to hydration mismatches.
60124

61125
If you have more than one Vue application instance of the same page, you can avoid ID conflicts by giving each app an ID prefix via [`app.config.idPrefix`](/api/application#app-config-idprefix).

src/guide/essentials/template-refs.md

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,32 @@ While Vue's declarative rendering model abstracts away most of the direct DOM op
1212

1313
<div class="composition-api">
1414

15-
To obtain the reference with Composition API, we need to declare a ref with a name that matches the template ref attribute's value:
15+
To obtain the reference with Composition API, we can use the [`useTemplateRef()`](/api/composition-api-helpers#usetemplateref) <sup class="vt-badge" data-text=
16+
"3.5+" /> helper:
17+
18+
```vue
19+
<script setup>
20+
import { useTemplateRef, onMounted } from 'vue'
21+
22+
// the first argument must match the ref value in the template
23+
const input = useTemplateRef('my-input')
24+
25+
onMounted(() => {
26+
input.value.focus()
27+
})
28+
</script>
29+
30+
<template>
31+
<input ref="my-input" />
32+
</template>
33+
```
34+
35+
When using TypeScript, Vue's IDE support and `vue-tsc` will automatically infer the type of `inputRef.value` based on what element or component the matching `ref` attribute is used on.
36+
37+
<details>
38+
<summary>Usage before 3.5</summary>
39+
40+
In versions before 3.5 where `useTemplateRef()` was not introduced, we need to declare a ref with a name that matches the template ref attribute's value:
1641

1742
```vue
1843
<script setup>
@@ -46,6 +71,8 @@ export default {
4671
}
4772
```
4873

74+
</details>
75+
4976
</div>
5077
<div class="options-api">
5178

@@ -95,6 +122,33 @@ See also: [Typing Template Refs](/guide/typescript/composition-api#typing-templa
95122

96123
When `ref` is used inside `v-for`, the corresponding ref should contain an Array value, which will be populated with the elements after mount:
97124

125+
```vue
126+
<script setup>
127+
import { ref, useTemplateRef, onMounted } from 'vue'
128+
129+
const list = ref([
130+
/* ... */
131+
])
132+
133+
const itemRefs = useTemplateRef('items')
134+
135+
onMounted(() => console.log(itemRefs.value))
136+
</script>
137+
138+
<template>
139+
<ul>
140+
<li v-for="item in list" ref="items">
141+
{{ item }}
142+
</li>
143+
</ul>
144+
</template>
145+
```
146+
147+
[Try it in the Playground](https://play.vuejs.org/#eNp9UsluwjAQ/ZWRLwQpDepyQoDUIg6t1EWUW91DFAZq6tiWF4oU5d87dtgqVRyyzLw3b+aN3bB7Y4ptQDZkI1dZYTw49MFMuBK10dZDAxZXOQSHC6yNLD3OY6zVsw7K4xJaWFldQ49UelxxVWnlPEhBr3GszT6uc7jJ4fazf4KFx5p0HFH+Kme9CLle4h6bZFkfxhNouAIoJVqfHQSKbSkDFnVpMhEpovC481NNVcr3SaWlZzTovJErCqgydaMIYBRk+tKfFLC9Wmk75iyqg1DJBWfRxT7pONvTAZom2YC23QsMpOg0B0l0NDh2YjnzjpyvxLrYOK1o3ckLZ5WujSBHr8YL2gxnw85lxEop9c9TynkbMD/kqy+svv/Jb9wu5jh7s+jQbpGzI+ZLu0byEuHZ+wvt6Ays9TJIYl8A5+i0DHHGjvYQ1JLGPuOlaR/TpRFqvXCzHR2BO5iKg0Zmm/ic0W2ZXrB+Gve2uEt1dJKs/QXbwePE)
148+
149+
<details>
150+
<summary>Usage before 3.5</summary>
151+
98152
```vue
99153
<script setup>
100154
import { ref, onMounted } from 'vue'
@@ -117,7 +171,7 @@ onMounted(() => console.log(itemRefs.value))
117171
</template>
118172
```
119173

120-
[Try it in the Playground](https://play.vuejs.org/#eNpFjs1qwzAQhF9l0CU2uDZtb8UOlJ576bXqwaQyCGRJyCsTEHr3rGwnOehnd2e+nSQ+vW/XqMSH6JdL0J6wKIr+LK2evQuEhKCmBs5+u2hJ/SNjCm7GiV0naaW9OLsQjOZrKNrq97XBW4P3v/o51qTmHzUtd8k+e0CrqsZwRpIWGI0KVN0N7TqaqNp59JUuEt2SutKXY5elmimZT9/t2Tk1F+z0ZiTFFdBHs738Mxrry+TCIEWhQ9sttRQl0tEsK6U4HEBKW3LkfDA6o3dst3H77rFM5BtTfm/P)
174+
</details>
121175

122176
</div>
123177
<div class="options-api">
@@ -173,6 +227,26 @@ Note we are using a dynamic `:ref` binding so we can pass it a function instead
173227

174228
<div class="composition-api">
175229

230+
```vue
231+
<script setup>
232+
import { useTemplateRef, onMounted } from 'vue'
233+
import Child from './Child.vue'
234+
235+
const childRef = useTemplateRef('child')
236+
237+
onMounted(() => {
238+
// childRef.value will hold an instance of <Child />
239+
})
240+
</script>
241+
242+
<template>
243+
<Child ref="child" />
244+
</template>
245+
```
246+
247+
<details>
248+
<summary>Usage before 3.5</summary>
249+
176250
```vue
177251
<script setup>
178252
import { ref, onMounted } from 'vue'
@@ -190,6 +264,8 @@ onMounted(() => {
190264
</template>
191265
```
192266

267+
</details>
268+
193269
</div>
194270
<div class="options-api">
195271

src/guide/typescript/composition-api.md

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,17 @@ const foo = inject('foo') as string
371371

372372
## Typing Template Refs {#typing-template-refs}
373373

374+
With Vue 3.5 and `@vue/language-tools` 2.1 (powering both the IDE language service and `vue-tsc`), the type of refs created by `useTemplateRef()` in SFCs can be **automatically inferred** for static refs based on what element the matching `ref` attribute is used on.
375+
376+
In cases where auto-inference is not possible, you can still cast the template ref to an explicit type via the generic argument:
377+
378+
```ts
379+
const el = useTemplateRef<HTMLInputElement>(null)
380+
```
381+
382+
<details>
383+
<summary>Usage before 3.5</summary>
384+
374385
Template refs should be created with an explicit generic type argument and an initial value of `null`:
375386

376387
```vue
@@ -389,50 +400,45 @@ onMounted(() => {
389400
</template>
390401
```
391402

403+
</details>
404+
392405
To get the right DOM interface you can check pages like [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#technical_summary).
393406

394407
Note that for strict type safety, it is necessary to use optional chaining or type guards when accessing `el.value`. This is because the initial ref value is `null` until the component is mounted, and it can also be set to `null` if the referenced element is unmounted by `v-if`.
395408

396409
## Typing Component Template Refs {#typing-component-template-refs}
397410

398-
Sometimes you might need to annotate a template ref for a child component in order to call its public method. For example, we have a `MyModal` child component with a method that opens the modal:
399-
400-
```vue
401-
<!-- MyModal.vue -->
402-
<script setup lang="ts">
403-
import { ref } from 'vue'
404-
405-
const isContentShown = ref(false)
406-
const open = () => (isContentShown.value = true)
411+
With Vue 3.5 and `@vue/language-tools` 2.1 (powering both the IDE language service and `vue-tsc`), the type of refs created by `useTemplateRef()` in SFCs can be **automatically inferred** for static refs based on what element or component the matching `ref` attribute is used on.
407412

408-
defineExpose({
409-
open
410-
})
411-
</script>
412-
```
413+
In cases where auto-inference is not possible (e.g. non-SFC usage or dynamic components), you can still cast the template ref to an explicit type via the generic argument.
413414

414-
In order to get the instance type of `MyModal`, we need to first get its type via `typeof`, then use TypeScript's built-in `InstanceType` utility to extract its instance type:
415+
In order to get the instance type of an imported component, we need to first get its type via `typeof`, then use TypeScript's built-in `InstanceType` utility to extract its instance type:
415416

416417
```vue{5}
417418
<!-- App.vue -->
418419
<script setup lang="ts">
419-
import MyModal from './MyModal.vue'
420+
import { useTemplateRef } from 'vue'
421+
import Foo from './Foo.vue'
422+
import Bar from './Bar.vue'
420423
421-
const modal = ref<InstanceType<typeof MyModal> | null>(null)
424+
type FooType = InstanceType<typeof Foo>
425+
type BarType = InstanceType<typeof Bar>
422426
423-
const openModal = () => {
424-
modal.value?.open()
425-
}
427+
const compRef = useTemplateRef<FooType | BarType>('comp')
426428
</script>
429+
430+
<template>
431+
<component :is="Math.random() > 0.5 ? Foo : Bar" ref="comp" />
432+
</template>
427433
```
428434

429435
In cases where the exact type of the component isn't available or isn't important, `ComponentPublicInstance` can be used instead. This will only include properties that are shared by all components, such as `$el`:
430436

431437
```ts
432-
import { ref } from 'vue'
438+
import { useTemplateRef } from 'vue'
433439
import type { ComponentPublicInstance } from 'vue'
434440

435-
const child = ref<ComponentPublicInstance | null>(null)
441+
const child = useTemplateRef<ComponentPublicInstance | null>(null)
436442
```
437443

438444
In cases where the component referenced is a [generic component](/guide/typescript/overview.html#generic-components), for instance `MyGenericModal`:
@@ -457,15 +463,16 @@ It needs to be referenced using `ComponentExposed` from the [`vue-component-type
457463
```vue
458464
<!-- App.vue -->
459465
<script setup lang="ts">
466+
import { useTemplateRef } from 'vue'
460467
import MyGenericModal from './MyGenericModal.vue'
468+
import type { ComponentExposed } from 'vue-component-type-helpers'
461469
462-
import type { ComponentExposed } from 'vue-component-type-helpers';
463-
464-
const modal = ref<ComponentExposed<typeof MyModal> | null>(null)
470+
const modal = useTemplateRef<ComponentExposed<typeof MyModal>>(null)
465471
466472
const openModal = () => {
467473
modal.value?.open('newValue')
468474
}
469475
</script>
470476
```
471477

478+
Note that with `@vue/language-tools` 2.1+, static template refs' types can be automatically inferred and the above is only needed in edge cases.

0 commit comments

Comments
 (0)