0
# Utility Functions
1
2
Utility functions provide additional tools for working with reactive values, type conversions, and advanced reactive operations that don't fit into the core reactive categories.
3
4
## Capabilities
5
6
### toRef()
7
8
Normalizes values/refs/getters into refs, or creates a ref for a property on a reactive object.
9
10
```typescript { .api }
11
/**
12
* Converts a value to a ref
13
* @param value - Value to convert to ref
14
* @returns A ref containing the value
15
*/
16
function toRef<T>(value: T): ToRef<T>;
17
18
/**
19
* Creates a ref for a property on a reactive object
20
* @param object - Reactive object
21
* @param key - Property key
22
* @returns A ref linked to the object property
23
*/
24
function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;
25
26
/**
27
* Creates a ref for a property with a default value
28
* @param object - Reactive object
29
* @param key - Property key
30
* @param defaultValue - Default value if property is undefined
31
* @returns A ref linked to the object property with default
32
*/
33
function toRef<T extends object, K extends keyof T>(
34
object: T,
35
key: K,
36
defaultValue: T[K]
37
): ToRef<Exclude<T[K], undefined>>;
38
```
39
40
**Usage Examples:**
41
42
```typescript
43
import { reactive, toRef, effect } from "@vue/reactivity";
44
45
const state = reactive({
46
count: 0,
47
user: { name: "Alice" },
48
tags: ["vue", "reactivity"]
49
});
50
51
// Create refs from reactive object properties
52
const countRef = toRef(state, "count");
53
const userRef = toRef(state, "user");
54
55
// Refs maintain connection to original object
56
effect(() => {
57
console.log(`Count ref: ${countRef.value}`);
58
});
59
60
countRef.value = 5; // Updates state.count and triggers effect
61
console.log(state.count); // 5
62
63
// Convert plain value to ref
64
const messageRef = toRef("Hello World");
65
console.log(messageRef.value); // "Hello World"
66
67
// Property ref with default value
68
const unknownProperty = toRef(state, "missing" as any, "default");
69
console.log(unknownProperty.value); // "default"
70
```
71
72
### toRefs()
73
74
Converts a reactive object to a plain object where each property is a ref that maintains reactivity connection to the original object.
75
76
```typescript { .api }
77
/**
78
* Converts a reactive object to refs for each property
79
* @param object - Reactive object to convert
80
* @returns Object with ref properties
81
*/
82
function toRefs<T extends object>(object: T): ToRefs<T>;
83
84
type ToRefs<T = any> = {
85
[K in keyof T]: ToRef<T[K]>;
86
};
87
```
88
89
**Usage Examples:**
90
91
```typescript
92
import { reactive, toRefs, effect } from "@vue/reactivity";
93
94
const state = reactive({
95
count: 0,
96
message: "Hello",
97
user: { name: "Alice" },
98
items: [1, 2, 3]
99
});
100
101
// Convert to refs - useful for destructuring without losing reactivity
102
const { count, message, user, items } = toRefs(state);
103
104
// All refs maintain connection to original state
105
effect(() => {
106
console.log(`${message.value}: ${count.value}`);
107
});
108
109
count.value = 10; // Updates state.count and triggers effect
110
message.value = "Count"; // Updates state.message and triggers effect
111
112
// Useful in composition functions
113
function useCounter(initialValue = 0) {
114
const state = reactive({
115
count: initialValue,
116
doubled: computed(() => state.count * 2)
117
});
118
119
const increment = () => state.count++;
120
const decrement = () => state.count--;
121
122
// Return refs for easy destructuring
123
return {
124
...toRefs(state),
125
increment,
126
decrement
127
};
128
}
129
130
const { count: myCount, doubled } = useCounter(5);
131
console.log(myCount.value); // 5
132
console.log(doubled.value); // 10
133
```
134
135
### toValue()
136
137
Normalizes values/refs/getters to values. Similar to `unref` but also handles getter functions.
138
139
```typescript { .api }
140
/**
141
* Normalizes values/refs/getters to values
142
* @param source - A ref, getter function, or plain value
143
* @returns The resolved value
144
*/
145
function toValue<T>(source: MaybeRefOrGetter<T>): T;
146
147
type MaybeRefOrGetter<T = any> = MaybeRef<T> | ComputedRef<T> | (() => T);
148
```
149
150
**Usage Examples:**
151
152
```typescript
153
import { ref, computed, toValue } from "@vue/reactivity";
154
155
const count = ref(42);
156
const doubled = computed(() => count.value * 2);
157
const getValue = () => 100;
158
const plainValue = 200;
159
160
// Works with all types
161
console.log(toValue(count)); // 42
162
console.log(toValue(doubled)); // 84
163
console.log(toValue(getValue)); // 100
164
console.log(toValue(plainValue)); // 200
165
166
// Useful for flexible function parameters
167
function useFlexibleValue<T>(source: MaybeRefOrGetter<T>) {
168
const value = toValue(source);
169
console.log("Current value:", value);
170
return value;
171
}
172
173
useFlexibleValue(count); // Works with ref
174
useFlexibleValue(doubled); // Works with computed
175
useFlexibleValue(getValue); // Works with getter
176
useFlexibleValue(300); // Works with plain value
177
```
178
179
### markRaw()
180
181
Marks an object so that it will never be converted to a proxy. Useful for performance optimization or compatibility with third-party libraries.
182
183
```typescript { .api }
184
/**
185
* Marks an object to never be converted to a proxy
186
* @param value - Object to mark as raw
187
* @returns The same object with a skip marker
188
*/
189
function markRaw<T extends object>(value: T): Raw<T>;
190
191
type Raw<T> = T & { [RawSymbol]?: true };
192
```
193
194
**Usage Examples:**
195
196
```typescript
197
import { reactive, markRaw, isReactive } from "@vue/reactivity";
198
199
// Large dataset that doesn't need reactivity
200
const largeDataSet = markRaw(new Array(10000).fill(0).map((_, i) => ({ id: i })));
201
202
// Third-party library instance
203
const thirdPartyLib = markRaw(new SomeLibrary());
204
205
// Date objects (often don't need reactivity)
206
const timestamp = markRaw(new Date());
207
208
const state = reactive({
209
count: 0,
210
data: largeDataSet, // Won't be made reactive
211
lib: thirdPartyLib, // Won't be made reactive
212
created: timestamp, // Won't be made reactive
213
config: { theme: "dark" } // Will be made reactive
214
});
215
216
console.log(isReactive(state.data)); // false
217
console.log(isReactive(state.lib)); // false
218
console.log(isReactive(state.created)); // false
219
console.log(isReactive(state.config)); // true
220
221
// Performance benefit: mutations to marked raw objects don't trigger effects
222
state.data.push({ id: 10000 }); // No reactive overhead
223
224
// Map and Set examples
225
const nonReactiveMap = markRaw(new Map([
226
["key1", "value1"],
227
["key2", "value2"]
228
]));
229
230
const reactiveState = reactive({
231
lookup: nonReactiveMap // Map operations won't trigger reactivity
232
});
233
234
// Direct Map operations are fast and don't trigger effects
235
reactiveState.lookup.set("key3", "value3");
236
reactiveState.lookup.delete("key1");
237
```
238
239
### customRef()
240
241
Creates a customized ref with explicit control over dependency tracking and updates.
242
243
```typescript { .api }
244
/**
245
* Creates a customized ref with explicit control over tracking and triggering
246
* @param factory - Factory function that receives track and trigger callbacks
247
* @returns A custom ref object
248
*/
249
function customRef<T>(factory: CustomRefFactory<T>): Ref<T>;
250
251
type CustomRefFactory<T> = (
252
track: () => void,
253
trigger: () => void
254
) => {
255
get: () => T;
256
set: (value: T) => void;
257
};
258
```
259
260
**Usage Examples:**
261
262
```typescript
263
import { customRef, effect } from "@vue/reactivity";
264
265
// Debounced ref that delays updates
266
function useDebouncedRef<T>(value: T, delay = 200) {
267
let timeout: number;
268
269
return customRef<T>((track, trigger) => {
270
return {
271
get() {
272
track(); // Track dependency
273
return value;
274
},
275
set(newValue: T) {
276
clearTimeout(timeout);
277
timeout = setTimeout(() => {
278
value = newValue;
279
trigger(); // Trigger updates after delay
280
}, delay);
281
}
282
};
283
});
284
}
285
286
const debouncedValue = useDebouncedRef("initial", 300);
287
288
effect(() => {
289
console.log("Debounced:", debouncedValue.value);
290
});
291
// Immediately logs: "Debounced: initial"
292
293
debouncedValue.value = "change1";
294
debouncedValue.value = "change2";
295
debouncedValue.value = "final";
296
// After 300ms delay, logs: "Debounced: final"
297
298
// Validated ref that only accepts valid values
299
function useValidatedRef<T>(
300
initialValue: T,
301
validator: (value: T) => boolean
302
) {
303
let _value = initialValue;
304
305
return customRef<T>((track, trigger) => {
306
return {
307
get() {
308
track();
309
return _value;
310
},
311
set(newValue: T) {
312
if (validator(newValue)) {
313
_value = newValue;
314
trigger();
315
} else {
316
console.warn("Invalid value rejected:", newValue);
317
}
318
}
319
};
320
});
321
}
322
323
const positiveNumber = useValidatedRef(1, (value: number) => value > 0);
324
325
effect(() => {
326
console.log("Valid number:", positiveNumber.value);
327
});
328
// Logs: "Valid number: 1"
329
330
positiveNumber.value = 5; // Logs: "Valid number: 5"
331
positiveNumber.value = -1; // Warns and doesn't update
332
positiveNumber.value = 10; // Logs: "Valid number: 10"
333
334
// Async ref that tracks loading state
335
function useAsyncRef<T>(asyncFn: () => Promise<T>, initialValue: T) {
336
let _value = initialValue;
337
let _loading = false;
338
let _error: Error | null = null;
339
340
const valueRef = customRef<T>((track, trigger) => ({
341
get() {
342
track();
343
return _value;
344
},
345
set(newValue: T) {
346
_value = newValue;
347
trigger();
348
}
349
}));
350
351
const loadingRef = customRef<boolean>((track, trigger) => ({
352
get() {
353
track();
354
return _loading;
355
},
356
set(newValue: boolean) {
357
_loading = newValue;
358
trigger();
359
}
360
}));
361
362
const load = async () => {
363
loadingRef.value = true;
364
_error = null;
365
366
try {
367
const result = await asyncFn();
368
valueRef.value = result;
369
} catch (error) {
370
_error = error as Error;
371
} finally {
372
loadingRef.value = false;
373
}
374
};
375
376
return {
377
value: valueRef,
378
loading: loadingRef,
379
error: () => _error,
380
load
381
};
382
}
383
```
384
385
### proxyRefs()
386
387
Returns a proxy that shallowly unwraps properties that are refs, allowing direct property access without `.value`.
388
389
```typescript { .api }
390
/**
391
* Creates a proxy that automatically unwraps ref properties
392
* @param objectWithRefs - Object containing ref properties
393
* @returns Proxy with automatic ref unwrapping
394
*/
395
function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T>;
396
397
type ShallowUnwrapRef<T> = {
398
[K in keyof T]: DistributeRef<T[K]>;
399
};
400
```
401
402
**Usage Examples:**
403
404
```typescript
405
import { ref, computed, proxyRefs, effect } from "@vue/reactivity";
406
407
// Object with mixed refs and plain values
408
const refs = {
409
count: ref(0),
410
message: ref("Hello"),
411
doubled: computed(() => refs.count.value * 2),
412
config: { theme: "dark" }, // Plain object
413
items: ref([1, 2, 3])
414
};
415
416
// Create proxy that unwraps refs
417
const proxy = proxyRefs(refs);
418
419
// Access without .value
420
console.log(proxy.count); // 0 (unwrapped from ref)
421
console.log(proxy.message); // "Hello" (unwrapped from ref)
422
console.log(proxy.doubled); // 0 (unwrapped from computed)
423
console.log(proxy.config); // { theme: "dark" } (plain value)
424
425
// Assignment unwraps refs automatically
426
proxy.count = 5; // Same as refs.count.value = 5
427
proxy.message = "Hi"; // Same as refs.message.value = "Hi"
428
429
// Effects work with the proxy
430
effect(() => {
431
console.log(`${proxy.message}: ${proxy.count} (doubled: ${proxy.doubled})`);
432
});
433
// Logs: "Hi: 5 (doubled: 10)"
434
435
proxy.count = 10; // Logs: "Hi: 10 (doubled: 20)"
436
437
// Useful for component setup return
438
function useComponent() {
439
const state = reactive({
440
loading: false,
441
data: null
442
});
443
444
const count = ref(0);
445
const doubled = computed(() => count.value * 2);
446
447
// Return proxy for easy template access
448
return proxyRefs({
449
...toRefs(state),
450
count,
451
doubled,
452
increment: () => count.value++
453
});
454
}
455
456
const component = useComponent();
457
// Template can use: component.loading, component.count, etc.
458
```
459
460
### Type Checking and Conversion Utilities
461
462
Additional utilities for working with reactive types and checking proxy states:
463
464
```typescript { .api }
465
/**
466
* Checks if a value is a proxy created by reactive(), readonly(), shallowReactive() or shallowReadonly()
467
* @param value - Value to check
468
* @returns True if the value is a proxy
469
*/
470
function isProxy(value: any): boolean;
471
472
/**
473
* Checks if a value is a shallow reactive proxy
474
* @param value - Value to check
475
* @returns True if the value is shallow
476
*/
477
function isShallow(value: unknown): boolean;
478
479
/**
480
* Returns a reactive proxy if the value is an object, otherwise the value itself
481
* @param value - Value to potentially make reactive
482
* @returns Reactive proxy or original value
483
*/
484
function toReactive<T>(value: T): T;
485
486
/**
487
* Returns a readonly proxy if the value is an object, otherwise the value itself
488
* @param value - Value to potentially make readonly
489
* @returns Readonly proxy or original value
490
*/
491
function toReadonly<T>(value: T): T;
492
```
493
494
**Usage Examples:**
495
496
```typescript
497
import {
498
reactive, readonly, shallowReactive, shallowReadonly,
499
isProxy, isShallow, isReactive, isReadonly,
500
toReactive, toReadonly, markRaw
501
} from "@vue/reactivity";
502
503
// Type checking utilities
504
const plainObj = { count: 0 };
505
const reactiveObj = reactive(plainObj);
506
const readonlyObj = readonly(plainObj);
507
const shallowObj = shallowReactive(plainObj);
508
const rawObj = markRaw({ data: [] });
509
510
console.log(isProxy(plainObj)); // false
511
console.log(isProxy(reactiveObj)); // true
512
console.log(isProxy(readonlyObj)); // true
513
console.log(isProxy(shallowObj)); // true
514
console.log(isProxy(rawObj)); // false
515
516
console.log(isShallow(reactiveObj)); // false (deep reactive)
517
console.log(isShallow(shallowObj)); // true
518
console.log(isShallow(readonlyObj)); // false
519
520
// toReactive converts objects to reactive, leaves primitives alone
521
const reactiveConverted = toReactive({ count: 0 }); // Becomes reactive
522
const reactiveNum = toReactive(42); // Stays 42
523
const reactiveStr = toReactive("hello"); // Stays "hello"
524
525
console.log(isReactive(reactiveConverted)); // true
526
console.log(isReactive(reactiveNum)); // false
527
console.log(isReactive(reactiveStr)); // false
528
529
// toReadonly converts objects to readonly, leaves primitives alone
530
const readonlyConverted = toReadonly({ count: 0 }); // Becomes readonly
531
const readonlyNum = toReadonly(42); // Stays 42
532
533
console.log(isReadonly(readonlyConverted)); // true
534
console.log(isReadonly(readonlyNum)); // false
535
536
// Useful for conditional reactivity
537
function makeReactiveIf<T>(value: T, condition: boolean): T {
538
return condition ? toReactive(value) : value;
539
}
540
541
const conditionallyReactive = makeReactiveIf({ data: [] }, true);
542
543
// Utility for checking proxy types
544
function getProxyType(value: any): string {
545
if (!isProxy(value)) return 'not-proxy';
546
if (isReadonly(value)) return isShallow(value) ? 'shallow-readonly' : 'readonly';
547
if (isReactive(value)) return isShallow(value) ? 'shallow-reactive' : 'reactive';
548
return 'unknown-proxy';
549
}
550
551
console.log(getProxyType(plainObj)); // 'not-proxy'
552
console.log(getProxyType(reactiveObj)); // 'reactive'
553
console.log(getProxyType(readonlyObj)); // 'readonly'
554
console.log(getProxyType(shallowObj)); // 'shallow-reactive'
555
```
556
557
## Advanced Utility Patterns
558
559
### Flexible Parameter Handling
560
561
Create utilities that work with different input types:
562
563
```typescript
564
import { toValue, MaybeRefOrGetter } from "@vue/reactivity";
565
566
function useFormatter<T>(
567
getValue: MaybeRefOrGetter<T>,
568
formatter: (value: T) => string
569
) {
570
return computed(() => {
571
const value = toValue(getValue);
572
return formatter(value);
573
});
574
}
575
576
// Works with refs, getters, or plain values
577
const count = ref(42);
578
const formatted1 = useFormatter(count, (n) => `Count: ${n}`);
579
const formatted2 = useFormatter(() => count.value * 2, (n) => `Double: ${n}`);
580
const formatted3 = useFormatter(100, (n) => `Static: ${n}`);
581
```
582
583
### Reactive Object Composition
584
585
Combine utilities for complex reactive patterns:
586
587
```typescript
588
import { reactive, toRefs, markRaw, customRef } from "@vue/reactivity";
589
590
function useStore<T extends object>(initialData: T) {
591
// Core reactive state
592
const state = reactive({ ...initialData });
593
594
// Non-reactive metadata
595
const meta = markRaw({
596
created: new Date(),
597
version: "1.0.0"
598
});
599
600
// Custom refs for special behavior
601
const lastUpdated = customRef<Date>((track, trigger) => {
602
let value = new Date();
603
return {
604
get() {
605
track();
606
return value;
607
},
608
set() {
609
value = new Date();
610
trigger();
611
}
612
};
613
});
614
615
// Update function that triggers lastUpdated
616
const updateState = (updates: Partial<T>) => {
617
Object.assign(state, updates);
618
lastUpdated.value = new Date(); // Triggers the custom ref
619
};
620
621
return {
622
...toRefs(state), // Individual property refs
623
state, // Full reactive state
624
meta, // Non-reactive metadata
625
lastUpdated, // Custom ref
626
updateState // Update function
627
};
628
}
629
630
const store = useStore({
631
count: 0,
632
message: "Hello"
633
});
634
635
// Use individual refs
636
store.count.value = 5;
637
638
// Or update via function
639
store.updateState({ message: "Updated" });
640
641
console.log(store.lastUpdated.value); // Shows update time
642
```
643
644
## Types
645
646
```typescript { .api }
647
// Conversion types
648
type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;
649
type ToRefs<T = any> = { [K in keyof T]: ToRef<T[K]> };
650
651
// Utility types
652
type MaybeRef<T = any> = T | Ref<T> | ShallowRef<T> | WritableComputedRef<T>;
653
type MaybeRefOrGetter<T = any> = MaybeRef<T> | ComputedRef<T> | (() => T);
654
655
// Unwrapping types
656
type ShallowUnwrapRef<T> = { [K in keyof T]: DistributeRef<T[K]> };
657
type DistributeRef<T> = T extends Ref<infer V> ? V : T;
658
659
// Raw type
660
type Raw<T> = T & { [RawSymbol]?: true };
661
662
// Custom ref factory
663
type CustomRefFactory<T> = (
664
track: () => void,
665
trigger: () => void
666
) => {
667
get: () => T;
668
set: (value: T) => void;
669
};
670
671
// Conditional type helpers
672
type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
673
```