Vue.js's standalone reactivity system providing reactive references, objects, computed values, effects, and watchers with fine-grained dependency tracking.
—
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.
Creates a reactive proxy of an object with deep reactive conversion. All nested objects and arrays become reactive, and refs are automatically unwrapped.
/**
* Creates a reactive proxy with deep reactive conversion
* @param target - Object to make reactive
* @returns Reactive proxy that tracks all property access and changes
*/
function reactive<T extends object>(target: T): Reactive<T>;
type Reactive<T> = UnwrapNestedRefs<T> & (T extends readonly any[] ? ReactiveMarker : {});Usage Examples:
import { reactive, effect } from "@vue/reactivity";
// Basic reactive object
const state = reactive({
count: 0,
message: "Hello Vue"
});
effect(() => {
console.log(`Count: ${state.count}, Message: ${state.message}`);
});
state.count++; // Triggers effect
state.message = "Hello World"; // Triggers effect
// Nested objects are automatically reactive
const user = reactive({
profile: {
name: "Alice",
settings: {
theme: "dark"
}
},
posts: []
});
// All levels are reactive
user.profile.name = "Bob"; // Triggers effects
user.posts.push({ title: "New Post" }); // Triggers effects
user.profile.settings.theme = "light"; // Triggers effects
// Arrays are reactive
const items = reactive([1, 2, 3]);
items.push(4); // Triggers effects
items[0] = 10; // Triggers effectsCreates 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.
/**
* Creates a readonly proxy with deep readonly conversion
* @param target - Object to make readonly
* @returns Deep readonly proxy that prevents mutations
*/
function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>>;
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};Usage Examples:
import { reactive, readonly } from "@vue/reactivity";
const original = reactive({
count: 0,
user: {
name: "Alice"
}
});
const readonlyState = readonly(original);
// Reading works fine
console.log(readonlyState.count); // 0
console.log(readonlyState.user.name); // "Alice"
// Mutations will warn in development and be ignored
// readonlyState.count = 1; // Warning in dev, ignored
// readonlyState.user.name = "Bob"; // Warning in dev, ignored
// Original can still be mutated
original.count = 5; // This works and readonlyState reflects the change
console.log(readonlyState.count); // 5Creates a shallow reactive proxy where only root-level properties are reactive. Nested objects are not converted to reactive.
/**
* Creates a shallow reactive proxy - only root level properties are reactive
* @param target - Object to make shallow reactive
* @returns Shallow reactive proxy
*/
function shallowReactive<T extends object>(target: T): ShallowReactive<T>;
type ShallowReactive<T> = T & ReactiveMarker;Usage Examples:
import { shallowReactive, effect } from "@vue/reactivity";
const state = shallowReactive({
count: 0,
user: {
name: "Alice" // This nested object is NOT reactive
}
});
effect(() => {
console.log(`Count: ${state.count}`);
});
effect(() => {
console.log(`User name: ${state.user.name}`);
});
state.count++; // Triggers first effect
state.user.name = "Bob"; // Does NOT trigger second effect (shallow)
// Replacing the entire object triggers effects
state.user = { name: "Charlie" }; // Triggers second effectCreates a shallow readonly proxy where only root-level properties are readonly. Nested objects can still be mutated.
/**
* Creates a shallow readonly proxy - only root level properties are readonly
* @param target - Object to make shallow readonly
* @returns Shallow readonly proxy
*/
function shallowReadonly<T extends object>(target: T): Readonly<T>;Usage Examples:
import { shallowReadonly } from "@vue/reactivity";
const state = {
count: 0,
user: {
name: "Alice"
}
};
const readonlyState = shallowReadonly(state);
// Root level mutations are blocked
// readonlyState.count = 1; // Warning in dev, ignored
// But nested mutations work (shallow)
readonlyState.user.name = "Bob"; // This works!
console.log(readonlyState.user.name); // "Bob"Checks if an object is a proxy created by reactive() or shallowReactive().
/**
* Checks if an object is a reactive proxy
* @param value - Value to check
* @returns True if the value is a reactive proxy
*/
function isReactive(value: unknown): boolean;Usage Examples:
import { reactive, shallowReactive, readonly, isReactive } from "@vue/reactivity";
const reactiveObj = reactive({ count: 0 });
const shallowObj = shallowReactive({ count: 0 });
const readonlyObj = readonly({ count: 0 });
const plainObj = { count: 0 };
console.log(isReactive(reactiveObj)); // true
console.log(isReactive(shallowObj)); // true
console.log(isReactive(readonlyObj)); // false (readonly, not reactive)
console.log(isReactive(plainObj)); // falseChecks if an object is a readonly proxy created by readonly() or shallowReadonly().
/**
* Checks if an object is a readonly proxy
* @param value - Value to check
* @returns True if the value is a readonly proxy
*/
function isReadonly(value: unknown): boolean;Usage Examples:
import { reactive, readonly, shallowReadonly, isReadonly } from "@vue/reactivity";
const reactiveObj = reactive({ count: 0 });
const readonlyObj = readonly({ count: 0 });
const shallowReadonlyObj = shallowReadonly({ count: 0 });
const plainObj = { count: 0 };
console.log(isReadonly(reactiveObj)); // false
console.log(isReadonly(readonlyObj)); // true
console.log(isReadonly(shallowReadonlyObj)); // true
console.log(isReadonly(plainObj)); // falseChecks if an object is a shallow reactive or readonly proxy.
/**
* Checks if an object is a shallow proxy (reactive or readonly)
* @param value - Value to check
* @returns True if the value is a shallow proxy
*/
function isShallow(value: unknown): boolean;Usage Examples:
import { reactive, shallowReactive, readonly, shallowReadonly, isShallow } from "@vue/reactivity";
const reactiveObj = reactive({ count: 0 });
const shallowReactiveObj = shallowReactive({ count: 0 });
const readonlyObj = readonly({ count: 0 });
const shallowReadonlyObj = shallowReadonly({ count: 0 });
console.log(isShallow(reactiveObj)); // false
console.log(isShallow(shallowReactiveObj)); // true
console.log(isShallow(readonlyObj)); // false
console.log(isShallow(shallowReadonlyObj)); // trueChecks if an object is any type of Vue-created proxy (reactive, readonly, shallow reactive, or shallow readonly).
/**
* Checks if an object is any type of Vue proxy
* @param value - Value to check
* @returns True if the value is any type of Vue proxy
*/
function isProxy(value: any): boolean;Usage Examples:
import { reactive, readonly, shallowReactive, isProxy } from "@vue/reactivity";
const reactiveObj = reactive({ count: 0 });
const readonlyObj = readonly({ count: 0 });
const shallowObj = shallowReactive({ count: 0 });
const plainObj = { count: 0 };
console.log(isProxy(reactiveObj)); // true
console.log(isProxy(readonlyObj)); // true
console.log(isProxy(shallowObj)); // true
console.log(isProxy(plainObj)); // falseReturns the raw, original object of a Vue-created proxy. Useful for getting the original object without proxy behavior.
/**
* Returns the raw original object of a Vue-created proxy
* @param observed - A Vue proxy object
* @returns The original object without proxy wrapper
*/
function toRaw<T>(observed: T): T;Usage Examples:
import { reactive, readonly, toRaw } from "@vue/reactivity";
const original = { count: 0, user: { name: "Alice" } };
const reactiveObj = reactive(original);
const readonlyObj = readonly(reactiveObj);
console.log(toRaw(reactiveObj) === original); // true
console.log(toRaw(readonlyObj) === original); // true
// Useful for passing non-reactive objects to third-party APIs
function saveToAPI(data: any) {
// Send raw object without reactivity
fetch("/api/save", {
method: "POST",
body: JSON.stringify(toRaw(data))
});
}
saveToAPI(reactiveObj); // Sends original objectMarks 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.
/**
* Marks an object to never be converted to a proxy
* @param value - Object to mark as raw
* @returns The same object with a skip marker
*/
function markRaw<T extends object>(value: T): Raw<T>;
type Raw<T> = T & { [RawSymbol]?: true };Usage Examples:
import { reactive, markRaw } from "@vue/reactivity";
// Mark an object to prevent reactivity
const nonReactiveObj = markRaw({
largeDataSet: new Array(10000).fill(0),
thirdPartyInstance: new SomeLibrary()
});
const state = reactive({
count: 0,
data: nonReactiveObj // This won't be made reactive
});
// state.count is reactive, but state.data is not
state.count++; // Triggers effects
state.data.largeDataSet.push(1); // Does NOT trigger effects (marked raw)
// Useful for large objects or third-party instances
const map = markRaw(new Map());
const reactiveState = reactive({
myMap: map // Map stays non-reactive for performance
});Utility function that returns a reactive proxy if the value is an object, otherwise returns the value itself.
/**
* Returns a reactive proxy if the value is an object, otherwise the value itself
* @param value - Value to potentially make reactive
* @returns Reactive proxy or original value
*/
const toReactive: <T extends unknown>(value: T) => T;Usage Examples:
import { toReactive } from "@vue/reactivity";
const obj = { count: 0 };
const num = 42;
const str = "hello";
const reactiveObj = toReactive(obj); // Returns reactive proxy
const reactiveNum = toReactive(num); // Returns 42 (primitive)
const reactiveStr = toReactive(str); // Returns "hello" (primitive)
console.log(isReactive(reactiveObj)); // true
console.log(isReactive(reactiveNum)); // false
console.log(isReactive(reactiveStr)); // falseUtility function that returns a readonly proxy if the value is an object, otherwise returns the value itself.
/**
* Returns a readonly proxy if the value is an object, otherwise the value itself
* @param value - Value to potentially make readonly
* @returns Readonly proxy or original value
*/
const toReadonly: <T extends unknown>(value: T) => DeepReadonly<T>;Usage Examples:
import { toReadonly } from "@vue/reactivity";
const obj = { count: 0 };
const num = 42;
const readonlyObj = toReadonly(obj); // Returns readonly proxy
const readonlyNum = toReadonly(num); // Returns 42 (primitive)
console.log(isReadonly(readonlyObj)); // true
console.log(isReadonly(readonlyNum)); // false (primitive)Vue's reactivity system provides special handling for JavaScript collections:
Arrays are fully reactive with instrumented methods:
import { reactive, effect } from "@vue/reactivity";
const arr = reactive([1, 2, 3]);
effect(() => {
console.log("Array length:", arr.length);
console.log("First item:", arr[0]);
});
// All these operations trigger effects
arr.push(4);
arr.pop();
arr[0] = 10;
arr.splice(1, 1, 20);
arr.sort();
arr.reverse();Maps are reactive for all operations:
import { reactive, effect } from "@vue/reactivity";
const map = reactive(new Map());
effect(() => {
console.log("Map size:", map.size);
console.log("Has 'key':", map.has("key"));
});
map.set("key", "value"); // Triggers effects
map.delete("key"); // Triggers effects
map.clear(); // Triggers effectsSets are reactive for all operations:
import { reactive, effect } from "@vue/reactivity";
const set = reactive(new Set());
effect(() => {
console.log("Set size:", set.size);
console.log("Has 'item':", set.has("item"));
});
set.add("item"); // Triggers effects
set.delete("item"); // Triggers effects
set.clear(); // Triggers effects// Reactive object types
type Reactive<T> = UnwrapNestedRefs<T> & (T extends readonly any[] ? ReactiveMarker : {});
type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P] };
type ShallowReactive<T> = T & ReactiveMarker;
// Utility types
type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;
type Raw<T> = T & { [RawSymbol]?: true };
// Target interface for reactive objects
interface Target {
[ReactiveFlags.SKIP]?: boolean;
[ReactiveFlags.IS_REACTIVE]?: boolean;
[ReactiveFlags.IS_READONLY]?: boolean;
[ReactiveFlags.IS_SHALLOW]?: boolean;
[ReactiveFlags.RAW]?: any;
}
// Marker interfaces
interface ReactiveMarker {
[ReactiveMarkerSymbol]?: void;
}Install with Tessl CLI
npx tessl i tessl/npm-vue--reactivity