0
# Reactive Objects
1
2
Reactive objects provide deep reactivity tracking for complex data structures using JavaScript Proxies. They automatically track property access and mutations, enabling fine-grained reactivity for objects, arrays, Maps, Sets, and other collections.
3
4
## Capabilities
5
6
### reactive()
7
8
Creates a reactive proxy of an object with deep reactive conversion. All nested objects and arrays become reactive, and refs are automatically unwrapped.
9
10
```typescript { .api }
11
/**
12
* Creates a reactive proxy with deep reactive conversion
13
* @param target - Object to make reactive
14
* @returns Reactive proxy that tracks all property access and changes
15
*/
16
function reactive<T extends object>(target: T): Reactive<T>;
17
18
type Reactive<T> = UnwrapNestedRefs<T> & (T extends readonly any[] ? ReactiveMarker : {});
19
```
20
21
**Usage Examples:**
22
23
```typescript
24
import { reactive, effect } from "@vue/reactivity";
25
26
// Basic reactive object
27
const state = reactive({
28
count: 0,
29
message: "Hello Vue"
30
});
31
32
effect(() => {
33
console.log(`Count: ${state.count}, Message: ${state.message}`);
34
});
35
36
state.count++; // Triggers effect
37
state.message = "Hello World"; // Triggers effect
38
39
// Nested objects are automatically reactive
40
const user = reactive({
41
profile: {
42
name: "Alice",
43
settings: {
44
theme: "dark"
45
}
46
},
47
posts: []
48
});
49
50
// All levels are reactive
51
user.profile.name = "Bob"; // Triggers effects
52
user.posts.push({ title: "New Post" }); // Triggers effects
53
user.profile.settings.theme = "light"; // Triggers effects
54
55
// Arrays are reactive
56
const items = reactive([1, 2, 3]);
57
items.push(4); // Triggers effects
58
items[0] = 10; // Triggers effects
59
```
60
61
### readonly()
62
63
Creates a readonly proxy to the original object with deep readonly conversion. The proxy has the same ref-unwrapping behavior as reactive objects but prevents mutations.
64
65
```typescript { .api }
66
/**
67
* Creates a readonly proxy with deep readonly conversion
68
* @param target - Object to make readonly
69
* @returns Deep readonly proxy that prevents mutations
70
*/
71
function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>>;
72
73
type DeepReadonly<T> = {
74
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
75
};
76
```
77
78
**Usage Examples:**
79
80
```typescript
81
import { reactive, readonly } from "@vue/reactivity";
82
83
const original = reactive({
84
count: 0,
85
user: {
86
name: "Alice"
87
}
88
});
89
90
const readonlyState = readonly(original);
91
92
// Reading works fine
93
console.log(readonlyState.count); // 0
94
console.log(readonlyState.user.name); // "Alice"
95
96
// Mutations will warn in development and be ignored
97
// readonlyState.count = 1; // Warning in dev, ignored
98
// readonlyState.user.name = "Bob"; // Warning in dev, ignored
99
100
// Original can still be mutated
101
original.count = 5; // This works and readonlyState reflects the change
102
console.log(readonlyState.count); // 5
103
```
104
105
### shallowReactive()
106
107
Creates a shallow reactive proxy where only root-level properties are reactive. Nested objects are not converted to reactive.
108
109
```typescript { .api }
110
/**
111
* Creates a shallow reactive proxy - only root level properties are reactive
112
* @param target - Object to make shallow reactive
113
* @returns Shallow reactive proxy
114
*/
115
function shallowReactive<T extends object>(target: T): ShallowReactive<T>;
116
117
type ShallowReactive<T> = T & ReactiveMarker;
118
```
119
120
**Usage Examples:**
121
122
```typescript
123
import { shallowReactive, effect } from "@vue/reactivity";
124
125
const state = shallowReactive({
126
count: 0,
127
user: {
128
name: "Alice" // This nested object is NOT reactive
129
}
130
});
131
132
effect(() => {
133
console.log(`Count: ${state.count}`);
134
});
135
136
effect(() => {
137
console.log(`User name: ${state.user.name}`);
138
});
139
140
state.count++; // Triggers first effect
141
state.user.name = "Bob"; // Does NOT trigger second effect (shallow)
142
143
// Replacing the entire object triggers effects
144
state.user = { name: "Charlie" }; // Triggers second effect
145
```
146
147
### shallowReadonly()
148
149
Creates a shallow readonly proxy where only root-level properties are readonly. Nested objects can still be mutated.
150
151
```typescript { .api }
152
/**
153
* Creates a shallow readonly proxy - only root level properties are readonly
154
* @param target - Object to make shallow readonly
155
* @returns Shallow readonly proxy
156
*/
157
function shallowReadonly<T extends object>(target: T): Readonly<T>;
158
```
159
160
**Usage Examples:**
161
162
```typescript
163
import { shallowReadonly } from "@vue/reactivity";
164
165
const state = {
166
count: 0,
167
user: {
168
name: "Alice"
169
}
170
};
171
172
const readonlyState = shallowReadonly(state);
173
174
// Root level mutations are blocked
175
// readonlyState.count = 1; // Warning in dev, ignored
176
177
// But nested mutations work (shallow)
178
readonlyState.user.name = "Bob"; // This works!
179
console.log(readonlyState.user.name); // "Bob"
180
```
181
182
### isReactive()
183
184
Checks if an object is a proxy created by `reactive()` or `shallowReactive()`.
185
186
```typescript { .api }
187
/**
188
* Checks if an object is a reactive proxy
189
* @param value - Value to check
190
* @returns True if the value is a reactive proxy
191
*/
192
function isReactive(value: unknown): boolean;
193
```
194
195
**Usage Examples:**
196
197
```typescript
198
import { reactive, shallowReactive, readonly, isReactive } from "@vue/reactivity";
199
200
const reactiveObj = reactive({ count: 0 });
201
const shallowObj = shallowReactive({ count: 0 });
202
const readonlyObj = readonly({ count: 0 });
203
const plainObj = { count: 0 };
204
205
console.log(isReactive(reactiveObj)); // true
206
console.log(isReactive(shallowObj)); // true
207
console.log(isReactive(readonlyObj)); // false (readonly, not reactive)
208
console.log(isReactive(plainObj)); // false
209
```
210
211
### isReadonly()
212
213
Checks if an object is a readonly proxy created by `readonly()` or `shallowReadonly()`.
214
215
```typescript { .api }
216
/**
217
* Checks if an object is a readonly proxy
218
* @param value - Value to check
219
* @returns True if the value is a readonly proxy
220
*/
221
function isReadonly(value: unknown): boolean;
222
```
223
224
**Usage Examples:**
225
226
```typescript
227
import { reactive, readonly, shallowReadonly, isReadonly } from "@vue/reactivity";
228
229
const reactiveObj = reactive({ count: 0 });
230
const readonlyObj = readonly({ count: 0 });
231
const shallowReadonlyObj = shallowReadonly({ count: 0 });
232
const plainObj = { count: 0 };
233
234
console.log(isReadonly(reactiveObj)); // false
235
console.log(isReadonly(readonlyObj)); // true
236
console.log(isReadonly(shallowReadonlyObj)); // true
237
console.log(isReadonly(plainObj)); // false
238
```
239
240
### isShallow()
241
242
Checks if an object is a shallow reactive or readonly proxy.
243
244
```typescript { .api }
245
/**
246
* Checks if an object is a shallow proxy (reactive or readonly)
247
* @param value - Value to check
248
* @returns True if the value is a shallow proxy
249
*/
250
function isShallow(value: unknown): boolean;
251
```
252
253
**Usage Examples:**
254
255
```typescript
256
import { reactive, shallowReactive, readonly, shallowReadonly, isShallow } from "@vue/reactivity";
257
258
const reactiveObj = reactive({ count: 0 });
259
const shallowReactiveObj = shallowReactive({ count: 0 });
260
const readonlyObj = readonly({ count: 0 });
261
const shallowReadonlyObj = shallowReadonly({ count: 0 });
262
263
console.log(isShallow(reactiveObj)); // false
264
console.log(isShallow(shallowReactiveObj)); // true
265
console.log(isShallow(readonlyObj)); // false
266
console.log(isShallow(shallowReadonlyObj)); // true
267
```
268
269
### isProxy()
270
271
Checks if an object is any type of Vue-created proxy (reactive, readonly, shallow reactive, or shallow readonly).
272
273
```typescript { .api }
274
/**
275
* Checks if an object is any type of Vue proxy
276
* @param value - Value to check
277
* @returns True if the value is any type of Vue proxy
278
*/
279
function isProxy(value: any): boolean;
280
```
281
282
**Usage Examples:**
283
284
```typescript
285
import { reactive, readonly, shallowReactive, isProxy } from "@vue/reactivity";
286
287
const reactiveObj = reactive({ count: 0 });
288
const readonlyObj = readonly({ count: 0 });
289
const shallowObj = shallowReactive({ count: 0 });
290
const plainObj = { count: 0 };
291
292
console.log(isProxy(reactiveObj)); // true
293
console.log(isProxy(readonlyObj)); // true
294
console.log(isProxy(shallowObj)); // true
295
console.log(isProxy(plainObj)); // false
296
```
297
298
### toRaw()
299
300
Returns the raw, original object of a Vue-created proxy. Useful for getting the original object without proxy behavior.
301
302
```typescript { .api }
303
/**
304
* Returns the raw original object of a Vue-created proxy
305
* @param observed - A Vue proxy object
306
* @returns The original object without proxy wrapper
307
*/
308
function toRaw<T>(observed: T): T;
309
```
310
311
**Usage Examples:**
312
313
```typescript
314
import { reactive, readonly, toRaw } from "@vue/reactivity";
315
316
const original = { count: 0, user: { name: "Alice" } };
317
const reactiveObj = reactive(original);
318
const readonlyObj = readonly(reactiveObj);
319
320
console.log(toRaw(reactiveObj) === original); // true
321
console.log(toRaw(readonlyObj) === original); // true
322
323
// Useful for passing non-reactive objects to third-party APIs
324
function saveToAPI(data: any) {
325
// Send raw object without reactivity
326
fetch("/api/save", {
327
method: "POST",
328
body: JSON.stringify(toRaw(data))
329
});
330
}
331
332
saveToAPI(reactiveObj); // Sends original object
333
```
334
335
### markRaw()
336
337
Marks an object so that it will never be converted to a proxy. Useful for objects that should remain non-reactive for performance or compatibility reasons.
338
339
```typescript { .api }
340
/**
341
* Marks an object to never be converted to a proxy
342
* @param value - Object to mark as raw
343
* @returns The same object with a skip marker
344
*/
345
function markRaw<T extends object>(value: T): Raw<T>;
346
347
type Raw<T> = T & { [RawSymbol]?: true };
348
```
349
350
**Usage Examples:**
351
352
```typescript
353
import { reactive, markRaw } from "@vue/reactivity";
354
355
// Mark an object to prevent reactivity
356
const nonReactiveObj = markRaw({
357
largeDataSet: new Array(10000).fill(0),
358
thirdPartyInstance: new SomeLibrary()
359
});
360
361
const state = reactive({
362
count: 0,
363
data: nonReactiveObj // This won't be made reactive
364
});
365
366
// state.count is reactive, but state.data is not
367
state.count++; // Triggers effects
368
state.data.largeDataSet.push(1); // Does NOT trigger effects (marked raw)
369
370
// Useful for large objects or third-party instances
371
const map = markRaw(new Map());
372
const reactiveState = reactive({
373
myMap: map // Map stays non-reactive for performance
374
});
375
```
376
377
### toReactive()
378
379
Utility function that returns a reactive proxy if the value is an object, otherwise returns the value itself.
380
381
```typescript { .api }
382
/**
383
* Returns a reactive proxy if the value is an object, otherwise the value itself
384
* @param value - Value to potentially make reactive
385
* @returns Reactive proxy or original value
386
*/
387
const toReactive: <T extends unknown>(value: T) => T;
388
```
389
390
**Usage Examples:**
391
392
```typescript
393
import { toReactive } from "@vue/reactivity";
394
395
const obj = { count: 0 };
396
const num = 42;
397
const str = "hello";
398
399
const reactiveObj = toReactive(obj); // Returns reactive proxy
400
const reactiveNum = toReactive(num); // Returns 42 (primitive)
401
const reactiveStr = toReactive(str); // Returns "hello" (primitive)
402
403
console.log(isReactive(reactiveObj)); // true
404
console.log(isReactive(reactiveNum)); // false
405
console.log(isReactive(reactiveStr)); // false
406
```
407
408
### toReadonly()
409
410
Utility function that returns a readonly proxy if the value is an object, otherwise returns the value itself.
411
412
```typescript { .api }
413
/**
414
* Returns a readonly proxy if the value is an object, otherwise the value itself
415
* @param value - Value to potentially make readonly
416
* @returns Readonly proxy or original value
417
*/
418
const toReadonly: <T extends unknown>(value: T) => DeepReadonly<T>;
419
```
420
421
**Usage Examples:**
422
423
```typescript
424
import { toReadonly } from "@vue/reactivity";
425
426
const obj = { count: 0 };
427
const num = 42;
428
429
const readonlyObj = toReadonly(obj); // Returns readonly proxy
430
const readonlyNum = toReadonly(num); // Returns 42 (primitive)
431
432
console.log(isReadonly(readonlyObj)); // true
433
console.log(isReadonly(readonlyNum)); // false (primitive)
434
```
435
436
## Reactive Collections
437
438
Vue's reactivity system provides special handling for JavaScript collections:
439
440
### Arrays
441
442
Arrays are fully reactive with instrumented methods:
443
444
```typescript
445
import { reactive, effect } from "@vue/reactivity";
446
447
const arr = reactive([1, 2, 3]);
448
449
effect(() => {
450
console.log("Array length:", arr.length);
451
console.log("First item:", arr[0]);
452
});
453
454
// All these operations trigger effects
455
arr.push(4);
456
arr.pop();
457
arr[0] = 10;
458
arr.splice(1, 1, 20);
459
arr.sort();
460
arr.reverse();
461
```
462
463
### Maps
464
465
Maps are reactive for all operations:
466
467
```typescript
468
import { reactive, effect } from "@vue/reactivity";
469
470
const map = reactive(new Map());
471
472
effect(() => {
473
console.log("Map size:", map.size);
474
console.log("Has 'key':", map.has("key"));
475
});
476
477
map.set("key", "value"); // Triggers effects
478
map.delete("key"); // Triggers effects
479
map.clear(); // Triggers effects
480
```
481
482
### Sets
483
484
Sets are reactive for all operations:
485
486
```typescript
487
import { reactive, effect } from "@vue/reactivity";
488
489
const set = reactive(new Set());
490
491
effect(() => {
492
console.log("Set size:", set.size);
493
console.log("Has 'item':", set.has("item"));
494
});
495
496
set.add("item"); // Triggers effects
497
set.delete("item"); // Triggers effects
498
set.clear(); // Triggers effects
499
```
500
501
## Types
502
503
```typescript { .api }
504
// Reactive object types
505
type Reactive<T> = UnwrapNestedRefs<T> & (T extends readonly any[] ? ReactiveMarker : {});
506
type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P] };
507
type ShallowReactive<T> = T & ReactiveMarker;
508
509
// Utility types
510
type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;
511
type Raw<T> = T & { [RawSymbol]?: true };
512
513
// Target interface for reactive objects
514
interface Target {
515
[ReactiveFlags.SKIP]?: boolean;
516
[ReactiveFlags.IS_REACTIVE]?: boolean;
517
[ReactiveFlags.IS_READONLY]?: boolean;
518
[ReactiveFlags.IS_SHALLOW]?: boolean;
519
[ReactiveFlags.RAW]?: any;
520
}
521
522
// Marker interfaces
523
interface ReactiveMarker {
524
[ReactiveMarkerSymbol]?: void;
525
}
526
```