0
# Computed Values
1
2
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.
3
4
## Capabilities
5
6
### computed()
7
8
Creates a computed ref that automatically tracks its dependencies and caches the result. The computed value only re-evaluates when its dependencies change.
9
10
```typescript { .api }
11
/**
12
* Creates a readonly computed ref from a getter function
13
* @param getter - Function that computes the value
14
* @param debugOptions - Optional debug configuration
15
* @returns A readonly computed ref
16
*/
17
function computed<T>(
18
getter: ComputedGetter<T>,
19
debugOptions?: DebuggerOptions
20
): ComputedRef<T>;
21
22
/**
23
* Creates a writable computed ref with getter and setter
24
* @param options - Object with get and set functions
25
* @param debugOptions - Optional debug configuration
26
* @returns A writable computed ref
27
*/
28
function computed<T, S = T>(
29
options: WritableComputedOptions<T, S>,
30
debugOptions?: DebuggerOptions
31
): WritableComputedRef<T, S>;
32
33
type ComputedGetter<T> = (oldValue?: T) => T;
34
type ComputedSetter<T> = (newValue: T) => void;
35
```
36
37
**Usage Examples:**
38
39
```typescript
40
import { ref, computed, effect } from "@vue/reactivity";
41
42
// Basic computed value
43
const count = ref(1);
44
const doubleCount = computed(() => count.value * 2);
45
46
console.log(doubleCount.value); // 2
47
count.value = 5;
48
console.log(doubleCount.value); // 10
49
50
// Computed with multiple dependencies
51
const firstName = ref("John");
52
const lastName = ref("Doe");
53
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
54
55
console.log(fullName.value); // "John Doe"
56
firstName.value = "Jane";
57
console.log(fullName.value); // "Jane Doe"
58
59
// Complex computed logic
60
const todos = ref([
61
{ id: 1, text: "Learn Vue", completed: false },
62
{ id: 2, text: "Build app", completed: true }
63
]);
64
65
const completedTodos = computed(() =>
66
todos.value.filter(todo => todo.completed)
67
);
68
69
const incompleteTodos = computed(() =>
70
todos.value.filter(todo => !todo.completed)
71
);
72
73
const todoStats = computed(() => ({
74
total: todos.value.length,
75
completed: completedTodos.value.length,
76
remaining: incompleteTodos.value.length,
77
progress: completedTodos.value.length / todos.value.length
78
}));
79
80
console.log(todoStats.value.progress); // 0.5
81
```
82
83
### Writable Computed
84
85
Create computed values that can be both read from and written to by providing getter and setter functions.
86
87
```typescript { .api }
88
interface WritableComputedOptions<T, S = T> {
89
get: ComputedGetter<T>;
90
set: ComputedSetter<S>;
91
}
92
93
interface WritableComputedRef<T, S = T> extends ComputedRef<T> {
94
set value(value: S);
95
}
96
```
97
98
**Usage Examples:**
99
100
```typescript
101
import { ref, computed } from "@vue/reactivity";
102
103
const firstName = ref("John");
104
const lastName = ref("Doe");
105
106
// Writable computed that combines and splits full name
107
const fullName = computed({
108
get: () => `${firstName.value} ${lastName.value}`,
109
set: (value: string) => {
110
const [first, last] = value.split(" ");
111
firstName.value = first || "";
112
lastName.value = last || "";
113
}
114
});
115
116
console.log(fullName.value); // "John Doe"
117
118
// Writing to computed updates the source refs
119
fullName.value = "Jane Smith";
120
console.log(firstName.value); // "Jane"
121
console.log(lastName.value); // "Smith"
122
123
// Writable computed for form data
124
const user = ref({
125
firstName: "Alice",
126
lastName: "Johnson",
127
email: "alice@example.com"
128
});
129
130
const displayName = computed({
131
get: () => `${user.value.firstName} ${user.value.lastName}`,
132
set: (name: string) => {
133
const [first, ...rest] = name.split(" ");
134
user.value.firstName = first || "";
135
user.value.lastName = rest.join(" ") || "";
136
}
137
});
138
139
displayName.value = "Bob Wilson"; // Updates user.firstName and user.lastName
140
```
141
142
### Computed with Side Effects
143
144
While computed values should generally be pure, you can use them with careful side effects for logging or debugging.
145
146
```typescript
147
import { ref, computed } from "@vue/reactivity";
148
149
const count = ref(0);
150
151
// Computed with debug logging
152
const expensiveComputation = computed(() => {
153
console.log("Computing expensive value..."); // Side effect for debugging
154
155
// Simulate expensive computation
156
let result = 0;
157
for (let i = 0; i < count.value * 1000; i++) {
158
result += Math.random();
159
}
160
161
return result;
162
});
163
164
// The computation only runs when count changes
165
console.log(expensiveComputation.value); // Runs computation
166
console.log(expensiveComputation.value); // Returns cached value
167
count.value = 2; // Invalidates cache
168
console.log(expensiveComputation.value); // Runs computation again
169
```
170
171
### Computed Error Handling
172
173
Handle errors in computed values gracefully:
174
175
```typescript
176
import { ref, computed } from "@vue/reactivity";
177
178
const jsonString = ref('{"name": "Alice"}');
179
180
const parsedData = computed(() => {
181
try {
182
return JSON.parse(jsonString.value);
183
} catch (error) {
184
console.warn("Invalid JSON:", error);
185
return null;
186
}
187
});
188
189
console.log(parsedData.value); // { name: "Alice" }
190
191
jsonString.value = "invalid json";
192
console.log(parsedData.value); // null (with warning)
193
```
194
195
### Debugging Computed Values
196
197
Use debug options to track computed value behavior:
198
199
```typescript
200
import { ref, computed } from "@vue/reactivity";
201
202
const count = ref(0);
203
204
const doubleCount = computed(
205
() => count.value * 2,
206
{
207
onTrack: (event) => {
208
console.log("Computed tracked:", event);
209
},
210
onTrigger: (event) => {
211
console.log("Computed triggered:", event);
212
}
213
}
214
);
215
216
// Access to track dependencies
217
console.log(doubleCount.value); // Logs tracking
218
219
// Change to trigger re-computation
220
count.value = 5; // Logs trigger
221
console.log(doubleCount.value); // New computed value
222
```
223
224
## Types
225
226
```typescript { .api }
227
// Core computed interfaces
228
interface ComputedRef<T = any> extends BaseComputedRef<T> {
229
readonly value: T;
230
}
231
232
interface WritableComputedRef<T, S = T> extends BaseComputedRef<T, S> {
233
set value(value: S);
234
[WritableComputedRefSymbol]: true;
235
}
236
237
interface BaseComputedRef<T, S = T> extends Ref<T, S> {
238
[ComputedRefSymbol]: true;
239
effect: ReactiveEffect<T>;
240
}
241
242
// Function types
243
type ComputedGetter<T> = (oldValue?: T) => T;
244
type ComputedSetter<T> = (newValue: T) => void;
245
246
// Options interface
247
interface WritableComputedOptions<T, S = T> {
248
get: ComputedGetter<T>;
249
set: ComputedSetter<S>;
250
}
251
252
// Debug options
253
interface DebuggerOptions {
254
onTrack?: (event: DebuggerEvent) => void;
255
onTrigger?: (event: DebuggerEvent) => void;
256
}
257
258
interface DebuggerEvent {
259
effect: ReactiveEffect;
260
target: object;
261
type: TrackOpTypes | TriggerOpTypes;
262
key: any;
263
newValue?: any;
264
oldValue?: any;
265
oldTarget?: Map<any, any> | Set<any>;
266
}
267
268
// Internal implementation (exported but marked as internal)
269
class ComputedRefImpl<T, S = T> implements WritableComputedRef<T, S> {
270
constructor(
271
getter: ComputedGetter<T>,
272
setter?: ComputedSetter<S>,
273
isReadonly?: boolean,
274
debugOptions?: DebuggerOptions
275
);
276
277
get value(): T;
278
set value(newValue: S);
279
280
readonly effect: ReactiveEffect<T>;
281
readonly [ComputedRefSymbol]: true;
282
readonly [RefSymbol]: true;
283
}
284
```
285
286
## Performance Considerations
287
288
### Lazy Evaluation
289
290
Computed values are lazy - they only compute when accessed:
291
292
```typescript
293
import { ref, computed } from "@vue/reactivity";
294
295
const count = ref(0);
296
297
// This computed is created but not evaluated yet
298
const expensiveValue = computed(() => {
299
console.log("Computing..."); // Won't run until accessed
300
return count.value * 1000;
301
});
302
303
// Now the computation runs
304
console.log(expensiveValue.value); // Logs "Computing..." then 0
305
```
306
307
### Caching Behavior
308
309
Computed values cache their results and only re-compute when dependencies change:
310
311
```typescript
312
import { ref, computed } from "@vue/reactivity";
313
314
const count = ref(1);
315
let computationCount = 0;
316
317
const doubled = computed(() => {
318
computationCount++;
319
console.log(`Computation #${computationCount}`);
320
return count.value * 2;
321
});
322
323
// First access computes the value
324
console.log(doubled.value); // Logs "Computation #1", returns 2
325
326
// Second access returns cached value
327
console.log(doubled.value); // Returns 2 (no computation log)
328
329
// Changing dependency invalidates cache
330
count.value = 3;
331
332
// Next access re-computes
333
console.log(doubled.value); // Logs "Computation #2", returns 6
334
```
335
336
### Avoiding Unnecessary Computations
337
338
Structure computed values to minimize unnecessary work:
339
340
```typescript
341
import { ref, computed } from "@vue/reactivity";
342
343
const users = ref([
344
{ id: 1, name: "Alice", active: true },
345
{ id: 2, name: "Bob", active: false },
346
{ id: 3, name: "Charlie", active: true }
347
]);
348
349
// Good: Computed values that can be independently cached
350
const activeUsers = computed(() =>
351
users.value.filter(user => user.active)
352
);
353
354
const userCount = computed(() => users.value.length);
355
const activeUserCount = computed(() => activeUsers.value.length);
356
357
// Less optimal: Single computed doing multiple calculations
358
const userStats = computed(() => ({
359
total: users.value.length,
360
active: users.value.filter(user => user.active).length, // Filters twice
361
inactive: users.value.filter(user => !user.active).length
362
}));
363
364
// Better: Use other computed values
365
const optimizedUserStats = computed(() => ({
366
total: userCount.value,
367
active: activeUserCount.value,
368
inactive: userCount.value - activeUserCount.value
369
}));
370
```