Vue.js's standalone reactivity system providing reactive references, objects, computed values, effects, and watchers with fine-grained dependency tracking.
—
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.
Normalizes values/refs/getters into refs, or creates a ref for a property on a reactive object.
/**
* Converts a value to a ref
* @param value - Value to convert to ref
* @returns A ref containing the value
*/
function toRef<T>(value: T): ToRef<T>;
/**
* Creates a ref for a property on a reactive object
* @param object - Reactive object
* @param key - Property key
* @returns A ref linked to the object property
*/
function toRef<T extends object, K extends keyof T>(object: T, key: K): ToRef<T[K]>;
/**
* Creates a ref for a property with a default value
* @param object - Reactive object
* @param key - Property key
* @param defaultValue - Default value if property is undefined
* @returns A ref linked to the object property with default
*/
function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
defaultValue: T[K]
): ToRef<Exclude<T[K], undefined>>;Usage Examples:
import { reactive, toRef, effect } from "@vue/reactivity";
const state = reactive({
count: 0,
user: { name: "Alice" },
tags: ["vue", "reactivity"]
});
// Create refs from reactive object properties
const countRef = toRef(state, "count");
const userRef = toRef(state, "user");
// Refs maintain connection to original object
effect(() => {
console.log(`Count ref: ${countRef.value}`);
});
countRef.value = 5; // Updates state.count and triggers effect
console.log(state.count); // 5
// Convert plain value to ref
const messageRef = toRef("Hello World");
console.log(messageRef.value); // "Hello World"
// Property ref with default value
const unknownProperty = toRef(state, "missing" as any, "default");
console.log(unknownProperty.value); // "default"Converts a reactive object to a plain object where each property is a ref that maintains reactivity connection to the original object.
/**
* Converts a reactive object to refs for each property
* @param object - Reactive object to convert
* @returns Object with ref properties
*/
function toRefs<T extends object>(object: T): ToRefs<T>;
type ToRefs<T = any> = {
[K in keyof T]: ToRef<T[K]>;
};Usage Examples:
import { reactive, toRefs, effect } from "@vue/reactivity";
const state = reactive({
count: 0,
message: "Hello",
user: { name: "Alice" },
items: [1, 2, 3]
});
// Convert to refs - useful for destructuring without losing reactivity
const { count, message, user, items } = toRefs(state);
// All refs maintain connection to original state
effect(() => {
console.log(`${message.value}: ${count.value}`);
});
count.value = 10; // Updates state.count and triggers effect
message.value = "Count"; // Updates state.message and triggers effect
// Useful in composition functions
function useCounter(initialValue = 0) {
const state = reactive({
count: initialValue,
doubled: computed(() => state.count * 2)
});
const increment = () => state.count++;
const decrement = () => state.count--;
// Return refs for easy destructuring
return {
...toRefs(state),
increment,
decrement
};
}
const { count: myCount, doubled } = useCounter(5);
console.log(myCount.value); // 5
console.log(doubled.value); // 10Normalizes values/refs/getters to values. Similar to unref but also handles getter functions.
/**
* Normalizes values/refs/getters to values
* @param source - A ref, getter function, or plain value
* @returns The resolved value
*/
function toValue<T>(source: MaybeRefOrGetter<T>): T;
type MaybeRefOrGetter<T = any> = MaybeRef<T> | ComputedRef<T> | (() => T);Usage Examples:
import { ref, computed, toValue } from "@vue/reactivity";
const count = ref(42);
const doubled = computed(() => count.value * 2);
const getValue = () => 100;
const plainValue = 200;
// Works with all types
console.log(toValue(count)); // 42
console.log(toValue(doubled)); // 84
console.log(toValue(getValue)); // 100
console.log(toValue(plainValue)); // 200
// Useful for flexible function parameters
function useFlexibleValue<T>(source: MaybeRefOrGetter<T>) {
const value = toValue(source);
console.log("Current value:", value);
return value;
}
useFlexibleValue(count); // Works with ref
useFlexibleValue(doubled); // Works with computed
useFlexibleValue(getValue); // Works with getter
useFlexibleValue(300); // Works with plain valueMarks an object so that it will never be converted to a proxy. Useful for performance optimization or compatibility with third-party libraries.
/**
* 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, isReactive } from "@vue/reactivity";
// Large dataset that doesn't need reactivity
const largeDataSet = markRaw(new Array(10000).fill(0).map((_, i) => ({ id: i })));
// Third-party library instance
const thirdPartyLib = markRaw(new SomeLibrary());
// Date objects (often don't need reactivity)
const timestamp = markRaw(new Date());
const state = reactive({
count: 0,
data: largeDataSet, // Won't be made reactive
lib: thirdPartyLib, // Won't be made reactive
created: timestamp, // Won't be made reactive
config: { theme: "dark" } // Will be made reactive
});
console.log(isReactive(state.data)); // false
console.log(isReactive(state.lib)); // false
console.log(isReactive(state.created)); // false
console.log(isReactive(state.config)); // true
// Performance benefit: mutations to marked raw objects don't trigger effects
state.data.push({ id: 10000 }); // No reactive overhead
// Map and Set examples
const nonReactiveMap = markRaw(new Map([
["key1", "value1"],
["key2", "value2"]
]));
const reactiveState = reactive({
lookup: nonReactiveMap // Map operations won't trigger reactivity
});
// Direct Map operations are fast and don't trigger effects
reactiveState.lookup.set("key3", "value3");
reactiveState.lookup.delete("key1");Creates a customized ref with explicit control over dependency tracking and updates.
/**
* Creates a customized ref with explicit control over tracking and triggering
* @param factory - Factory function that receives track and trigger callbacks
* @returns A custom ref object
*/
function customRef<T>(factory: CustomRefFactory<T>): Ref<T>;
type CustomRefFactory<T> = (
track: () => void,
trigger: () => void
) => {
get: () => T;
set: (value: T) => void;
};Usage Examples:
import { customRef, effect } from "@vue/reactivity";
// Debounced ref that delays updates
function useDebouncedRef<T>(value: T, delay = 200) {
let timeout: number;
return customRef<T>((track, trigger) => {
return {
get() {
track(); // Track dependency
return value;
},
set(newValue: T) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger(); // Trigger updates after delay
}, delay);
}
};
});
}
const debouncedValue = useDebouncedRef("initial", 300);
effect(() => {
console.log("Debounced:", debouncedValue.value);
});
// Immediately logs: "Debounced: initial"
debouncedValue.value = "change1";
debouncedValue.value = "change2";
debouncedValue.value = "final";
// After 300ms delay, logs: "Debounced: final"
// Validated ref that only accepts valid values
function useValidatedRef<T>(
initialValue: T,
validator: (value: T) => boolean
) {
let _value = initialValue;
return customRef<T>((track, trigger) => {
return {
get() {
track();
return _value;
},
set(newValue: T) {
if (validator(newValue)) {
_value = newValue;
trigger();
} else {
console.warn("Invalid value rejected:", newValue);
}
}
};
});
}
const positiveNumber = useValidatedRef(1, (value: number) => value > 0);
effect(() => {
console.log("Valid number:", positiveNumber.value);
});
// Logs: "Valid number: 1"
positiveNumber.value = 5; // Logs: "Valid number: 5"
positiveNumber.value = -1; // Warns and doesn't update
positiveNumber.value = 10; // Logs: "Valid number: 10"
// Async ref that tracks loading state
function useAsyncRef<T>(asyncFn: () => Promise<T>, initialValue: T) {
let _value = initialValue;
let _loading = false;
let _error: Error | null = null;
const valueRef = customRef<T>((track, trigger) => ({
get() {
track();
return _value;
},
set(newValue: T) {
_value = newValue;
trigger();
}
}));
const loadingRef = customRef<boolean>((track, trigger) => ({
get() {
track();
return _loading;
},
set(newValue: boolean) {
_loading = newValue;
trigger();
}
}));
const load = async () => {
loadingRef.value = true;
_error = null;
try {
const result = await asyncFn();
valueRef.value = result;
} catch (error) {
_error = error as Error;
} finally {
loadingRef.value = false;
}
};
return {
value: valueRef,
loading: loadingRef,
error: () => _error,
load
};
}Returns a proxy that shallowly unwraps properties that are refs, allowing direct property access without .value.
/**
* Creates a proxy that automatically unwraps ref properties
* @param objectWithRefs - Object containing ref properties
* @returns Proxy with automatic ref unwrapping
*/
function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T>;
type ShallowUnwrapRef<T> = {
[K in keyof T]: DistributeRef<T[K]>;
};Usage Examples:
import { ref, computed, proxyRefs, effect } from "@vue/reactivity";
// Object with mixed refs and plain values
const refs = {
count: ref(0),
message: ref("Hello"),
doubled: computed(() => refs.count.value * 2),
config: { theme: "dark" }, // Plain object
items: ref([1, 2, 3])
};
// Create proxy that unwraps refs
const proxy = proxyRefs(refs);
// Access without .value
console.log(proxy.count); // 0 (unwrapped from ref)
console.log(proxy.message); // "Hello" (unwrapped from ref)
console.log(proxy.doubled); // 0 (unwrapped from computed)
console.log(proxy.config); // { theme: "dark" } (plain value)
// Assignment unwraps refs automatically
proxy.count = 5; // Same as refs.count.value = 5
proxy.message = "Hi"; // Same as refs.message.value = "Hi"
// Effects work with the proxy
effect(() => {
console.log(`${proxy.message}: ${proxy.count} (doubled: ${proxy.doubled})`);
});
// Logs: "Hi: 5 (doubled: 10)"
proxy.count = 10; // Logs: "Hi: 10 (doubled: 20)"
// Useful for component setup return
function useComponent() {
const state = reactive({
loading: false,
data: null
});
const count = ref(0);
const doubled = computed(() => count.value * 2);
// Return proxy for easy template access
return proxyRefs({
...toRefs(state),
count,
doubled,
increment: () => count.value++
});
}
const component = useComponent();
// Template can use: component.loading, component.count, etc.Additional utilities for working with reactive types and checking proxy states:
/**
* Checks if a value is a proxy created by reactive(), readonly(), shallowReactive() or shallowReadonly()
* @param value - Value to check
* @returns True if the value is a proxy
*/
function isProxy(value: any): boolean;
/**
* Checks if a value is a shallow reactive proxy
* @param value - Value to check
* @returns True if the value is shallow
*/
function isShallow(value: unknown): boolean;
/**
* 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
*/
function toReactive<T>(value: T): T;
/**
* 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
*/
function toReadonly<T>(value: T): T;Usage Examples:
import {
reactive, readonly, shallowReactive, shallowReadonly,
isProxy, isShallow, isReactive, isReadonly,
toReactive, toReadonly, markRaw
} from "@vue/reactivity";
// Type checking utilities
const plainObj = { count: 0 };
const reactiveObj = reactive(plainObj);
const readonlyObj = readonly(plainObj);
const shallowObj = shallowReactive(plainObj);
const rawObj = markRaw({ data: [] });
console.log(isProxy(plainObj)); // false
console.log(isProxy(reactiveObj)); // true
console.log(isProxy(readonlyObj)); // true
console.log(isProxy(shallowObj)); // true
console.log(isProxy(rawObj)); // false
console.log(isShallow(reactiveObj)); // false (deep reactive)
console.log(isShallow(shallowObj)); // true
console.log(isShallow(readonlyObj)); // false
// toReactive converts objects to reactive, leaves primitives alone
const reactiveConverted = toReactive({ count: 0 }); // Becomes reactive
const reactiveNum = toReactive(42); // Stays 42
const reactiveStr = toReactive("hello"); // Stays "hello"
console.log(isReactive(reactiveConverted)); // true
console.log(isReactive(reactiveNum)); // false
console.log(isReactive(reactiveStr)); // false
// toReadonly converts objects to readonly, leaves primitives alone
const readonlyConverted = toReadonly({ count: 0 }); // Becomes readonly
const readonlyNum = toReadonly(42); // Stays 42
console.log(isReadonly(readonlyConverted)); // true
console.log(isReadonly(readonlyNum)); // false
// Useful for conditional reactivity
function makeReactiveIf<T>(value: T, condition: boolean): T {
return condition ? toReactive(value) : value;
}
const conditionallyReactive = makeReactiveIf({ data: [] }, true);
// Utility for checking proxy types
function getProxyType(value: any): string {
if (!isProxy(value)) return 'not-proxy';
if (isReadonly(value)) return isShallow(value) ? 'shallow-readonly' : 'readonly';
if (isReactive(value)) return isShallow(value) ? 'shallow-reactive' : 'reactive';
return 'unknown-proxy';
}
console.log(getProxyType(plainObj)); // 'not-proxy'
console.log(getProxyType(reactiveObj)); // 'reactive'
console.log(getProxyType(readonlyObj)); // 'readonly'
console.log(getProxyType(shallowObj)); // 'shallow-reactive'Create utilities that work with different input types:
import { toValue, MaybeRefOrGetter } from "@vue/reactivity";
function useFormatter<T>(
getValue: MaybeRefOrGetter<T>,
formatter: (value: T) => string
) {
return computed(() => {
const value = toValue(getValue);
return formatter(value);
});
}
// Works with refs, getters, or plain values
const count = ref(42);
const formatted1 = useFormatter(count, (n) => `Count: ${n}`);
const formatted2 = useFormatter(() => count.value * 2, (n) => `Double: ${n}`);
const formatted3 = useFormatter(100, (n) => `Static: ${n}`);Combine utilities for complex reactive patterns:
import { reactive, toRefs, markRaw, customRef } from "@vue/reactivity";
function useStore<T extends object>(initialData: T) {
// Core reactive state
const state = reactive({ ...initialData });
// Non-reactive metadata
const meta = markRaw({
created: new Date(),
version: "1.0.0"
});
// Custom refs for special behavior
const lastUpdated = customRef<Date>((track, trigger) => {
let value = new Date();
return {
get() {
track();
return value;
},
set() {
value = new Date();
trigger();
}
};
});
// Update function that triggers lastUpdated
const updateState = (updates: Partial<T>) => {
Object.assign(state, updates);
lastUpdated.value = new Date(); // Triggers the custom ref
};
return {
...toRefs(state), // Individual property refs
state, // Full reactive state
meta, // Non-reactive metadata
lastUpdated, // Custom ref
updateState // Update function
};
}
const store = useStore({
count: 0,
message: "Hello"
});
// Use individual refs
store.count.value = 5;
// Or update via function
store.updateState({ message: "Updated" });
console.log(store.lastUpdated.value); // Shows update time// Conversion types
type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>>;
type ToRefs<T = any> = { [K in keyof T]: ToRef<T[K]> };
// Utility types
type MaybeRef<T = any> = T | Ref<T> | ShallowRef<T> | WritableComputedRef<T>;
type MaybeRefOrGetter<T = any> = MaybeRef<T> | ComputedRef<T> | (() => T);
// Unwrapping types
type ShallowUnwrapRef<T> = { [K in keyof T]: DistributeRef<T[K]> };
type DistributeRef<T> = T extends Ref<infer V> ? V : T;
// Raw type
type Raw<T> = T & { [RawSymbol]?: true };
// Custom ref factory
type CustomRefFactory<T> = (
track: () => void,
trigger: () => void
) => {
get: () => T;
set: (value: T) => void;
};
// Conditional type helpers
type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;Install with Tessl CLI
npx tessl i tessl/npm-vue--reactivity