Vue.js's standalone reactivity system providing reactive references, objects, computed values, effects, and watchers with fine-grained dependency tracking.
—
Computed values are cached derived values that automatically update when their dependencies change. They provide an efficient way to create values based on reactive state while avoiding unnecessary computations.
Creates a computed ref that automatically tracks its dependencies and caches the result. The computed value only re-evaluates when its dependencies change.
/**
* Creates a readonly computed ref from a getter function
* @param getter - Function that computes the value
* @param debugOptions - Optional debug configuration
* @returns A readonly computed ref
*/
function computed<T>(
getter: ComputedGetter<T>,
debugOptions?: DebuggerOptions
): ComputedRef<T>;
/**
* Creates a writable computed ref with getter and setter
* @param options - Object with get and set functions
* @param debugOptions - Optional debug configuration
* @returns A writable computed ref
*/
function computed<T, S = T>(
options: WritableComputedOptions<T, S>,
debugOptions?: DebuggerOptions
): WritableComputedRef<T, S>;
type ComputedGetter<T> = (oldValue?: T) => T;
type ComputedSetter<T> = (newValue: T) => void;Usage Examples:
import { ref, computed, effect } from "@vue/reactivity";
// Basic computed value
const count = ref(1);
const doubleCount = computed(() => count.value * 2);
console.log(doubleCount.value); // 2
count.value = 5;
console.log(doubleCount.value); // 10
// Computed with multiple dependencies
const firstName = ref("John");
const lastName = ref("Doe");
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
console.log(fullName.value); // "John Doe"
firstName.value = "Jane";
console.log(fullName.value); // "Jane Doe"
// Complex computed logic
const todos = ref([
{ id: 1, text: "Learn Vue", completed: false },
{ id: 2, text: "Build app", completed: true }
]);
const completedTodos = computed(() =>
todos.value.filter(todo => todo.completed)
);
const incompleteTodos = computed(() =>
todos.value.filter(todo => !todo.completed)
);
const todoStats = computed(() => ({
total: todos.value.length,
completed: completedTodos.value.length,
remaining: incompleteTodos.value.length,
progress: completedTodos.value.length / todos.value.length
}));
console.log(todoStats.value.progress); // 0.5Create computed values that can be both read from and written to by providing getter and setter functions.
interface WritableComputedOptions<T, S = T> {
get: ComputedGetter<T>;
set: ComputedSetter<S>;
}
interface WritableComputedRef<T, S = T> extends ComputedRef<T> {
set value(value: S);
}Usage Examples:
import { ref, computed } from "@vue/reactivity";
const firstName = ref("John");
const lastName = ref("Doe");
// Writable computed that combines and splits full name
const fullName = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (value: string) => {
const [first, last] = value.split(" ");
firstName.value = first || "";
lastName.value = last || "";
}
});
console.log(fullName.value); // "John Doe"
// Writing to computed updates the source refs
fullName.value = "Jane Smith";
console.log(firstName.value); // "Jane"
console.log(lastName.value); // "Smith"
// Writable computed for form data
const user = ref({
firstName: "Alice",
lastName: "Johnson",
email: "alice@example.com"
});
const displayName = computed({
get: () => `${user.value.firstName} ${user.value.lastName}`,
set: (name: string) => {
const [first, ...rest] = name.split(" ");
user.value.firstName = first || "";
user.value.lastName = rest.join(" ") || "";
}
});
displayName.value = "Bob Wilson"; // Updates user.firstName and user.lastNameWhile computed values should generally be pure, you can use them with careful side effects for logging or debugging.
import { ref, computed } from "@vue/reactivity";
const count = ref(0);
// Computed with debug logging
const expensiveComputation = computed(() => {
console.log("Computing expensive value..."); // Side effect for debugging
// Simulate expensive computation
let result = 0;
for (let i = 0; i < count.value * 1000; i++) {
result += Math.random();
}
return result;
});
// The computation only runs when count changes
console.log(expensiveComputation.value); // Runs computation
console.log(expensiveComputation.value); // Returns cached value
count.value = 2; // Invalidates cache
console.log(expensiveComputation.value); // Runs computation againHandle errors in computed values gracefully:
import { ref, computed } from "@vue/reactivity";
const jsonString = ref('{"name": "Alice"}');
const parsedData = computed(() => {
try {
return JSON.parse(jsonString.value);
} catch (error) {
console.warn("Invalid JSON:", error);
return null;
}
});
console.log(parsedData.value); // { name: "Alice" }
jsonString.value = "invalid json";
console.log(parsedData.value); // null (with warning)Use debug options to track computed value behavior:
import { ref, computed } from "@vue/reactivity";
const count = ref(0);
const doubleCount = computed(
() => count.value * 2,
{
onTrack: (event) => {
console.log("Computed tracked:", event);
},
onTrigger: (event) => {
console.log("Computed triggered:", event);
}
}
);
// Access to track dependencies
console.log(doubleCount.value); // Logs tracking
// Change to trigger re-computation
count.value = 5; // Logs trigger
console.log(doubleCount.value); // New computed value// Core computed interfaces
interface ComputedRef<T = any> extends BaseComputedRef<T> {
readonly value: T;
}
interface WritableComputedRef<T, S = T> extends BaseComputedRef<T, S> {
set value(value: S);
[WritableComputedRefSymbol]: true;
}
interface BaseComputedRef<T, S = T> extends Ref<T, S> {
[ComputedRefSymbol]: true;
effect: ReactiveEffect<T>;
}
// Function types
type ComputedGetter<T> = (oldValue?: T) => T;
type ComputedSetter<T> = (newValue: T) => void;
// Options interface
interface WritableComputedOptions<T, S = T> {
get: ComputedGetter<T>;
set: ComputedSetter<S>;
}
// Debug options
interface DebuggerOptions {
onTrack?: (event: DebuggerEvent) => void;
onTrigger?: (event: DebuggerEvent) => void;
}
interface DebuggerEvent {
effect: ReactiveEffect;
target: object;
type: TrackOpTypes | TriggerOpTypes;
key: any;
newValue?: any;
oldValue?: any;
oldTarget?: Map<any, any> | Set<any>;
}
// Internal implementation (exported but marked as internal)
class ComputedRefImpl<T, S = T> implements WritableComputedRef<T, S> {
constructor(
getter: ComputedGetter<T>,
setter?: ComputedSetter<S>,
isReadonly?: boolean,
debugOptions?: DebuggerOptions
);
get value(): T;
set value(newValue: S);
readonly effect: ReactiveEffect<T>;
readonly [ComputedRefSymbol]: true;
readonly [RefSymbol]: true;
}Computed values are lazy - they only compute when accessed:
import { ref, computed } from "@vue/reactivity";
const count = ref(0);
// This computed is created but not evaluated yet
const expensiveValue = computed(() => {
console.log("Computing..."); // Won't run until accessed
return count.value * 1000;
});
// Now the computation runs
console.log(expensiveValue.value); // Logs "Computing..." then 0Computed values cache their results and only re-compute when dependencies change:
import { ref, computed } from "@vue/reactivity";
const count = ref(1);
let computationCount = 0;
const doubled = computed(() => {
computationCount++;
console.log(`Computation #${computationCount}`);
return count.value * 2;
});
// First access computes the value
console.log(doubled.value); // Logs "Computation #1", returns 2
// Second access returns cached value
console.log(doubled.value); // Returns 2 (no computation log)
// Changing dependency invalidates cache
count.value = 3;
// Next access re-computes
console.log(doubled.value); // Logs "Computation #2", returns 6Structure computed values to minimize unnecessary work:
import { ref, computed } from "@vue/reactivity";
const users = ref([
{ id: 1, name: "Alice", active: true },
{ id: 2, name: "Bob", active: false },
{ id: 3, name: "Charlie", active: true }
]);
// Good: Computed values that can be independently cached
const activeUsers = computed(() =>
users.value.filter(user => user.active)
);
const userCount = computed(() => users.value.length);
const activeUserCount = computed(() => activeUsers.value.length);
// Less optimal: Single computed doing multiple calculations
const userStats = computed(() => ({
total: users.value.length,
active: users.value.filter(user => user.active).length, // Filters twice
inactive: users.value.filter(user => !user.active).length
}));
// Better: Use other computed values
const optimizedUserStats = computed(() => ({
total: userCount.value,
active: activeUserCount.value,
inactive: userCount.value - activeUserCount.value
}));Install with Tessl CLI
npx tessl i tessl/npm-vue--reactivity