0
# Composition Helpers
1
2
Vue provides helper utilities for composition API usage including template refs, v-model integration, unique ID generation, and setup context access.
3
4
## Capabilities
5
6
### Template References
7
8
Create reactive references to template elements and component instances.
9
10
```typescript { .api }
11
/**
12
* Creates a template ref for accessing DOM elements or component instances
13
* @param key - Template ref key (must match ref attribute in template)
14
* @returns Template ref object
15
*/
16
function useTemplateRef<T = any>(key: string): TemplateRef<T>;
17
18
interface TemplateRef<T = any> extends Ref<T> {
19
readonly value: T;
20
}
21
```
22
23
**Usage Examples:**
24
25
```typescript
26
import { defineComponent, useTemplateRef, onMounted } from "@vue/runtime-core";
27
28
const MyComponent = defineComponent({
29
setup() {
30
// Create template refs
31
const inputRef = useTemplateRef<HTMLInputElement>('input');
32
const childRef = useTemplateRef<ComponentInstance>('child');
33
34
onMounted(() => {
35
// Access DOM element
36
if (inputRef.value) {
37
inputRef.value.focus();
38
console.log('Input value:', inputRef.value.value);
39
}
40
41
// Access child component instance
42
if (childRef.value) {
43
// Call child component methods
44
childRef.value.someMethod();
45
}
46
});
47
48
return {
49
inputRef,
50
childRef
51
};
52
},
53
54
template: `
55
<div>
56
<input ref="input" type="text" />
57
<ChildComponent ref="child" />
58
</div>
59
`
60
});
61
```
62
63
### Model References
64
65
Create reactive references for v-model bindings with built-in getter/setter logic.
66
67
```typescript { .api }
68
/**
69
* Creates a model ref for v-model integration
70
* @param props - Component props object
71
* @param name - Model property name
72
* @param options - Model configuration options
73
* @returns Model ref with getter/setter
74
*/
75
function useModel<T>(
76
props: Record<string, any>,
77
name: string,
78
options?: UseModelOptions
79
): ModelRef<T>;
80
81
interface UseModelOptions {
82
/**
83
* Default value when prop is undefined
84
*/
85
defaultValue?: any;
86
87
/**
88
* Whether the model should be deeply reactive
89
*/
90
deep?: boolean;
91
92
/**
93
* Transform function for incoming value
94
*/
95
get?(value: any): any;
96
97
/**
98
* Transform function for outgoing value
99
*/
100
set?(value: any): any;
101
}
102
103
interface ModelRef<T> extends Ref<T> {
104
readonly [ModelRefMarkerSymbol]: true;
105
}
106
```
107
108
**Usage Examples:**
109
110
```typescript
111
import { defineComponent, useModel } from "@vue/runtime-core";
112
113
// Basic v-model component
114
const CustomInput = defineComponent({
115
props: {
116
modelValue: String,
117
placeholder: String
118
},
119
emits: ['update:modelValue'],
120
121
setup(props, { emit }) {
122
// Create model ref that syncs with v-model
123
const model = useModel(props, 'modelValue');
124
125
return {
126
model
127
};
128
},
129
130
template: `
131
<input
132
v-model="model"
133
:placeholder="placeholder"
134
/>
135
`
136
});
137
138
// Multiple v-model support
139
const MultiModelComponent = defineComponent({
140
props: {
141
firstName: String,
142
lastName: String,
143
age: Number
144
},
145
emits: ['update:firstName', 'update:lastName', 'update:age'],
146
147
setup(props) {
148
const firstName = useModel(props, 'firstName');
149
const lastName = useModel(props, 'lastName');
150
const age = useModel(props, 'age', {
151
defaultValue: 0,
152
get: (value) => Number(value) || 0,
153
set: (value) => Math.max(0, Math.floor(value))
154
});
155
156
return {
157
firstName,
158
lastName,
159
age
160
};
161
}
162
});
163
164
// Usage:
165
// <MultiModelComponent
166
// v-model:firstName="user.firstName"
167
// v-model:lastName="user.lastName"
168
// v-model:age="user.age"
169
// />
170
```
171
172
### Unique ID Generation
173
174
Generate unique IDs for accessibility and form associations.
175
176
```typescript { .api }
177
/**
178
* Generates a unique ID for the current component instance
179
* @returns Unique string ID
180
*/
181
function useId(): string;
182
```
183
184
**Usage Examples:**
185
186
```typescript
187
import { defineComponent, useId } from "@vue/runtime-core";
188
189
const FormField = defineComponent({
190
props: {
191
label: String,
192
required: Boolean
193
},
194
195
setup(props) {
196
const id = useId();
197
198
return {
199
id,
200
inputId: `input-${id}`,
201
labelId: `label-${id}`,
202
errorId: `error-${id}`
203
};
204
},
205
206
template: `
207
<div>
208
<label :id="labelId" :for="inputId">
209
{{ label }}
210
<span v-if="required" aria-label="required">*</span>
211
</label>
212
<input
213
:id="inputId"
214
:aria-labelledby="labelId"
215
:aria-describedby="errorId"
216
:required="required"
217
/>
218
<div :id="errorId" class="error" aria-live="polite">
219
<!-- Error messages -->
220
</div>
221
</div>
222
`
223
});
224
225
// Multiple IDs in one component
226
const MultiIdComponent = defineComponent({
227
setup() {
228
const formId = useId();
229
const modalId = useId();
230
const tabsId = useId();
231
232
return {
233
formId: `form-${formId}`,
234
modalId: `modal-${modalId}`,
235
tabsId: `tabs-${tabsId}`
236
};
237
}
238
});
239
```
240
241
### Setup Context Access
242
243
Access component slots and attributes within setup function.
244
245
```typescript { .api }
246
/**
247
* Gets setup context slots
248
* @returns Component slots object
249
*/
250
function useSlots(): Slots;
251
252
/**
253
* Gets setup context attrs (non-prop attributes)
254
* @returns Component attrs object
255
*/
256
function useAttrs(): Data;
257
258
interface Slots {
259
[name: string]: Slot | undefined;
260
default?: Slot;
261
}
262
263
type Slot<T = any> = (...args: any[]) => VNode[];
264
265
type Data = Record<string, unknown>;
266
```
267
268
**Usage Examples:**
269
270
```typescript
271
import { defineComponent, useSlots, useAttrs, h } from "@vue/runtime-core";
272
273
const WrapperComponent = defineComponent({
274
setup() {
275
const slots = useSlots();
276
const attrs = useAttrs();
277
278
// Check available slots
279
const hasHeader = !!slots.header;
280
const hasFooter = !!slots.footer;
281
const hasDefault = !!slots.default;
282
283
console.log('Available slots:', Object.keys(slots));
284
console.log('Component attrs:', attrs);
285
286
return () => h('div', {
287
class: 'wrapper',
288
...attrs // Forward all non-prop attributes
289
}, [
290
hasHeader && h('header', { class: 'header' }, slots.header!()),
291
hasDefault && h('main', { class: 'content' }, slots.default!()),
292
hasFooter && h('footer', { class: 'footer' }, slots.footer!())
293
]);
294
}
295
});
296
297
// Dynamic slot rendering
298
const DynamicSlots = defineComponent({
299
setup() {
300
const slots = useSlots();
301
302
return () => {
303
const slotElements = Object.entries(slots).map(([name, slot]) => {
304
if (slot) {
305
return h('div', {
306
key: name,
307
class: `slot-${name}`
308
}, slot());
309
}
310
return null;
311
}).filter(Boolean);
312
313
return h('div', { class: 'dynamic-slots' }, slotElements);
314
};
315
}
316
});
317
318
// Attrs manipulation
319
const AttrsComponent = defineComponent({
320
props: {
321
title: String
322
},
323
324
setup(props) {
325
const attrs = useAttrs();
326
327
// Filter out certain attributes
328
const filteredAttrs = computed(() => {
329
const { class: className, style, ...rest } = attrs;
330
return rest;
331
});
332
333
// Combine with custom attributes
334
const combinedAttrs = computed(() => ({
335
...filteredAttrs.value,
336
'data-component': 'attrs-component',
337
'aria-label': props.title || 'Custom component'
338
}));
339
340
return {
341
combinedAttrs
342
};
343
}
344
});
345
```
346
347
### Advanced Patterns
348
349
```typescript
350
// Composable using multiple helpers
351
function useFormField(name: string) {
352
const id = useId();
353
const attrs = useAttrs();
354
355
const fieldId = `field-${id}`;
356
const labelId = `label-${id}`;
357
const errorId = `error-${id}`;
358
359
// Extract validation attributes
360
const validationAttrs = computed(() => {
361
const { required, minlength, maxlength, pattern } = attrs;
362
return { required, minlength, maxlength, pattern };
363
});
364
365
return {
366
fieldId,
367
labelId,
368
errorId,
369
validationAttrs
370
};
371
}
372
373
// Ref forwarding pattern
374
const ForwardRefComponent = defineComponent({
375
setup(_, { expose }) {
376
const elementRef = useTemplateRef<HTMLElement>('element');
377
378
// Expose ref to parent
379
expose({
380
focus: () => elementRef.value?.focus(),
381
blur: () => elementRef.value?.blur(),
382
element: elementRef
383
});
384
385
return {
386
elementRef
387
};
388
}
389
});
390
```
391
392
## Types
393
394
```typescript { .api }
395
interface TemplateRef<T = any> extends Ref<T> {
396
/**
397
* Template ref marker for type discrimination
398
*/
399
readonly [TemplateRefSymbol]: true;
400
}
401
402
interface ModelRef<T = any> extends Ref<T> {
403
/**
404
* Model ref marker for type discrimination
405
*/
406
readonly [ModelRefMarkerSymbol]: true;
407
}
408
409
type UseModelOptions = {
410
defaultValue?: unknown;
411
deep?: boolean;
412
get?(value: unknown): unknown;
413
set?(value: unknown): unknown;
414
};
415
416
interface Slots {
417
[name: string]: Slot | undefined;
418
}
419
420
type Slot<T extends Record<string, any> = any> = (
421
props: T
422
) => VNode[];
423
424
type Data = Record<string, unknown>;
425
```