Skip to content

Commit c516731

Browse files
committed
template refs
1 parent 0638254 commit c516731

File tree

2 files changed

+232
-3
lines changed

2 files changed

+232
-3
lines changed

src/.vitepress/config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,11 @@ const sidebar = {
132132
{ text: 'Event Handling', link: '/guide/essentials/event-handling' },
133133
{ text: 'Form Input Bindings', link: '/guide/essentials/forms' },
134134
{ text: 'Watchers', link: '/guide/essentials/watchers' },
135+
{ text: 'Template Refs', link: '/guide/essentials/template-refs' },
135136
{
136137
text: 'Components Basics',
137138
link: '/guide/essentials/component-basics'
138-
},
139-
{ text: 'Template Refs', link: '/guide/essentials/template-refs' }
139+
}
140140
]
141141
},
142142
{

src/guide/essentials/template-refs.md

Lines changed: 230 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,230 @@
1-
# Template Refs
1+
# Template Refs
2+
3+
While Vue's declarative rendering model abstracts away most of the direct DOM operations for you, there may still be cases where we need direct access to the underlying DOM elements. To achieve this, we can use the special `ref` attribute:
4+
5+
```vue-html
6+
<input ref="input">
7+
```
8+
9+
`ref` is a special attribute that is similar to `key` and `is`. It allows us to obtain a direct reference to a specific DOM element or child component instance after it's mounted. This may be useful when you want to, for example, programmatically focus an input on component mount, or initialize a 3rd party library on an element.
10+
11+
## Accessing the Refs
12+
13+
<div class="composition-api">
14+
15+
To obtain the reference with Composition API, we need to declare a ref with the same name:
16+
17+
```vue
18+
<script setup>
19+
import { ref, onMounted } from 'vue'
20+
21+
// declare a ref to hold the element reference
22+
// the name must match template ref value
23+
const input = ref(null)
24+
25+
onMounted(() => {
26+
input.value.focus()
27+
})
28+
</script>
29+
30+
<template>
31+
<input ref="input" />
32+
</template>
33+
```
34+
35+
</div>
36+
<div class="options-api">
37+
38+
The resulting ref is exposed on `this.$refs`:
39+
40+
```vue
41+
<script setup>
42+
export default {
43+
mounted() {
44+
this.$refs.input.focus()
45+
}
46+
}
47+
</script>
48+
49+
<template>
50+
<input ref="input" />
51+
</template>
52+
```
53+
54+
</div>
55+
56+
Note that you can only access the ref **after the component is mounted.** If you try to access `$refs.input` in a template expression, it will be `null` on the first render. This is because the element doesn't exist until after the first render!
57+
58+
<div class="composition-api">
59+
If you are trying to watch the changes of a template ref, make sure to account for the case where the ref has `null` value:
60+
61+
```js
62+
watchEffect(() => {
63+
if (input.value) {
64+
input.value.focus()
65+
} else {
66+
// not mounted yet, or the element was unmounted (e.g. by v-if)
67+
}
68+
})
69+
```
70+
71+
</div>
72+
73+
## Function Refs
74+
75+
Instead of a string key, the `ref` attribute can also be bound to a function, which will be called on each component update. The function receives the element reference as the first argument:
76+
77+
```vue-html
78+
<input :ref="(el) => { /* assign el to a property or ref */ }">
79+
```
80+
81+
You can, of course, use a method instead of an inline function.
82+
83+
## Refs inside `v-for`
84+
85+
Unlike Vue 2, template refs in Vue 3 will not automatically populate an array when used inside `v-for`. You can, however, achieve that using function refs:
86+
87+
<div class="composition-api">
88+
89+
```vue
90+
<script setup>
91+
import { ref, onBeforeUpdate } from 'vue'
92+
93+
const list = ref([
94+
/* ... */
95+
])
96+
97+
let itemRefs = []
98+
99+
onBeforeUpdate(() => {
100+
// reset the array before each update
101+
itemRefs = []
102+
})
103+
</script>
104+
105+
<template>
106+
<div v-for="item in list" :ref="(el) => el && itemRefs.push(el)"></div>
107+
</template>
108+
```
109+
110+
</div>
111+
<div class="options-api">
112+
113+
```vue
114+
<script>
115+
export default {
116+
data() {
117+
return {
118+
list: [
119+
/* ... */
120+
],
121+
itemRefs: []
122+
}
123+
},
124+
beforeUpdate() {
125+
// reset the array before an update
126+
this.itemRefs = []
127+
}
128+
}
129+
</script>
130+
131+
<template>
132+
<div v-for="item in list" :ref="(el) => el && itemRefs.push(el)"></div>
133+
</template>
134+
```
135+
136+
</div>
137+
138+
## Ref on Component
139+
140+
> This section assumes knowledge of [Components](/guide/essentials/component-basics). Feel free to skip it and come back later.
141+
142+
`ref` can also be used on a child component. In this case the reference will be that of a component instance:
143+
144+
<div class="composition-api">
145+
146+
```vue
147+
<script setup>
148+
import { ref, onMounted } from 'vue'
149+
import Child from './Child.vue'
150+
151+
const child = ref(null)
152+
153+
onMounted(() => {
154+
// child.value will hold an instance of <Child />
155+
})
156+
</script>
157+
158+
<template>
159+
<Child ref="child" />
160+
</template>
161+
```
162+
163+
</div>
164+
<div class="options-api">
165+
166+
```vue
167+
<script>
168+
import Child from './Child.vue'
169+
170+
export default {
171+
components: {
172+
Child
173+
},
174+
mounted() {
175+
// this.$refs.child will hold an instance of <Child />
176+
}
177+
}
178+
</script>
179+
180+
<template>
181+
<Child ref="child" />
182+
</template>
183+
```
184+
185+
</div>
186+
187+
If the child component is using Options API or not using `<script setup>`, the referenced instance will be identical to the child component's `this`, which means the parent component will have full access to every property and method of the child component. This makes it easy to create tightly coupled implementation details between the parent and the child, so component refs should be only used when absolutely needed - in most cases, you should try to implement parent / child interactions using the standard props and emit interfaces first.
188+
189+
<div class="composition-api">
190+
191+
An exception here is that components using `<script setup>` are **private by default**: a parent component referencing a child component using `<script setup>` won't be able to access anything unless the child component choose to expose a public interface using the `defineExpose` macro:
192+
193+
```vue
194+
<script setup>
195+
import { ref } from 'vue'
196+
197+
const a = 1
198+
const b = ref(2)
199+
200+
defineExpose({
201+
a,
202+
b
203+
})
204+
</script>
205+
```
206+
207+
When a parent gets an instance of this component via template refs, the retrieved instance will be of the shape `{ a: number, b: number }` (refs are automatically unwrapped just like on normal instances).
208+
209+
</div>
210+
<div class="options-api">
211+
212+
The `expose` option can be used to limit the access to a child instance:
213+
214+
```js
215+
export default {
216+
expose: ['publicData', 'publicMethod'],
217+
data() {
218+
publicData: 'foo',
219+
privateData: 'bar'
220+
},
221+
methods: {
222+
publicMethod() { /* ... */ },
223+
privateMethod() { /* ... */ }
224+
}
225+
}
226+
```
227+
228+
In the above example, a parent referencing this component via template ref will only be able to access `publicData` and `publicMethod`.
229+
230+
</div>

0 commit comments

Comments
 (0)