0
# Watchers and Effects
1
2
Advanced watching system for tracking reactive data changes with configurable timing, cleanup, and side effects. Watchers enable you to perform side effects in response to reactive state changes.
3
4
## Capabilities
5
6
### Watch Function
7
8
Watches reactive data sources and executes a callback when they change. Supports watching single sources, multiple sources, and deep watching.
9
10
```typescript { .api }
11
/**
12
* Watches a single reactive source
13
* @param source - Reactive source to watch
14
* @param callback - Callback executed when source changes
15
* @param options - Watch configuration options
16
* @returns Function to stop watching
17
*/
18
function watch<T>(
19
source: WatchSource<T>,
20
callback: WatchCallback<T>,
21
options?: WatchOptions
22
): WatchStopHandle;
23
24
/**
25
* Watches multiple reactive sources
26
* @param sources - Array of reactive sources to watch
27
* @param callback - Callback executed when any source changes
28
* @param options - Watch configuration options
29
* @returns Function to stop watching
30
*/
31
function watch<T extends readonly unknown[]>(
32
sources: readonly [...T],
33
callback: WatchCallback<MapSources<T>>,
34
options?: WatchOptions
35
): WatchStopHandle;
36
37
/**
38
* Watches a reactive object with immediate and deep options
39
* @param source - Reactive object to watch
40
* @param callback - Callback executed when object changes
41
* @param options - Watch configuration options
42
* @returns Function to stop watching
43
*/
44
function watch<T extends object>(
45
source: T,
46
callback: WatchCallback<T>,
47
options?: WatchOptions<true>
48
): WatchStopHandle;
49
50
type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T);
51
type WatchCallback<T> = (value: T, oldValue: T, onInvalidate: InvalidateCbRegistrator) => void;
52
type WatchStopHandle = () => void;
53
type InvalidateCbRegistrator = (fn: () => void) => void;
54
```
55
56
**Usage Examples:**
57
58
```typescript
59
import { ref, reactive, computed, watch } from "@vue/composition-api";
60
61
// Watch a single ref
62
const count = ref(0);
63
const stopWatching = watch(count, (newValue, oldValue) => {
64
console.log(`Count changed from ${oldValue} to ${newValue}`);
65
});
66
67
// Watch multiple sources
68
const name = ref("Alice");
69
const age = ref(25);
70
watch([name, age], ([newName, newAge], [oldName, oldAge]) => {
71
console.log(`User changed from ${oldName}(${oldAge}) to ${newName}(${newAge})`);
72
});
73
74
// Watch computed property
75
const doubleCount = computed(() => count.value * 2);
76
watch(doubleCount, (newValue) => {
77
console.log(`Double count is now: ${newValue}`);
78
});
79
80
// Watch reactive object (deep by default)
81
const user = reactive({ name: "Bob", profile: { age: 30 } });
82
watch(user, (newUser, oldUser) => {
83
console.log("User object changed:", newUser);
84
});
85
86
// Stop watching when needed
87
stopWatching();
88
```
89
90
### Watch Options
91
92
Configuration options for customizing watch behavior including timing, depth, and immediate execution.
93
94
```typescript { .api }
95
interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
96
immediate?: Immediate;
97
deep?: boolean;
98
}
99
100
interface WatchOptionsBase {
101
flush?: FlushMode;
102
}
103
104
type FlushMode = "pre" | "post" | "sync";
105
```
106
107
**Watch with Options:**
108
109
```typescript
110
import { ref, watch } from "@vue/composition-api";
111
112
const data = ref({ count: 0, nested: { value: 1 } });
113
114
// Immediate execution
115
watch(
116
data,
117
(newValue, oldValue) => {
118
console.log("Data changed:", newValue);
119
},
120
{ immediate: true } // Callback runs immediately with current value
121
);
122
123
// Deep watching (default for objects)
124
watch(
125
data,
126
(newValue) => {
127
console.log("Deep change detected");
128
},
129
{ deep: true }
130
);
131
132
// Flush timing control
133
watch(
134
data,
135
(newValue) => {
136
// Runs after DOM updates
137
console.log("DOM has been updated");
138
},
139
{ flush: "post" }
140
);
141
142
watch(
143
data,
144
(newValue) => {
145
// Runs before DOM updates (default)
146
console.log("Before DOM update");
147
},
148
{ flush: "pre" }
149
);
150
151
watch(
152
data,
153
(newValue) => {
154
// Runs synchronously
155
console.log("Sync execution");
156
},
157
{ flush: "sync" }
158
);
159
```
160
161
### Watch Effects
162
163
Runs an effect function immediately and re-runs it whenever its reactive dependencies change. Unlike watch, watchEffect doesn't need explicit sources.
164
165
```typescript { .api }
166
/**
167
* Runs effect immediately and re-runs when dependencies change
168
* @param effect - Effect function to run
169
* @returns Function to stop the effect
170
*/
171
function watchEffect(effect: WatchEffect): WatchStopHandle;
172
173
/**
174
* Post-flush watchEffect (runs after DOM updates)
175
* @param effect - Effect function to run
176
* @returns Function to stop the effect
177
*/
178
function watchPostEffect(effect: WatchEffect): WatchStopHandle;
179
180
/**
181
* Sync watchEffect (runs synchronously)
182
* @param effect - Effect function to run
183
* @returns Function to stop the effect
184
*/
185
function watchSyncEffect(effect: WatchEffect): WatchStopHandle;
186
187
type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void;
188
```
189
190
**Usage Examples:**
191
192
```typescript
193
import { ref, reactive, watchEffect, watchPostEffect } from "@vue/composition-api";
194
195
const count = ref(0);
196
const user = reactive({ name: "Alice", age: 25 });
197
198
// Basic watchEffect - automatically tracks dependencies
199
const stop = watchEffect(() => {
200
console.log(`Count is ${count.value}, user is ${user.name}`);
201
// This effect will re-run when count or user.name changes
202
});
203
204
// Effect with cleanup
205
watchEffect(async (onInvalidate) => {
206
const controller = new AbortController();
207
208
// Register cleanup function
209
onInvalidate(() => {
210
controller.abort();
211
});
212
213
try {
214
const response = await fetch(`/api/user/${user.name}`, {
215
signal: controller.signal,
216
});
217
const userData = await response.json();
218
console.log("User data:", userData);
219
} catch (error) {
220
if (error.name !== "AbortError") {
221
console.error("Failed to fetch user data:", error);
222
}
223
}
224
});
225
226
// Post-flush effect (runs after DOM updates)
227
watchPostEffect(() => {
228
// Access DOM elements after updates
229
const element = document.getElementById("counter");
230
if (element) {
231
element.textContent = count.value.toString();
232
}
233
});
234
235
// Stop the effect when needed
236
stop();
237
```
238
239
### Advanced Watching Patterns
240
241
Complex watching scenarios including conditional watching, async effects, and performance optimization.
242
243
**Conditional Watching:**
244
245
```typescript
246
import { ref, computed, watch } from "@vue/composition-api";
247
248
const isEnabled = ref(false);
249
const data = ref("initial");
250
251
// Only watch when condition is met
252
const conditionalStop = watch(
253
data,
254
(newValue) => {
255
if (isEnabled.value) {
256
console.log("Data changed:", newValue);
257
}
258
}
259
);
260
261
// Alternative: Dynamic watcher creation
262
let stopWatcher: (() => void) | null = null;
263
264
watch(isEnabled, (enabled) => {
265
if (enabled && !stopWatcher) {
266
stopWatcher = watch(data, (newValue) => {
267
console.log("Conditional watch:", newValue);
268
});
269
} else if (!enabled && stopWatcher) {
270
stopWatcher();
271
stopWatcher = null;
272
}
273
});
274
```
275
276
**Debounced Watching:**
277
278
```typescript
279
import { ref, watch } from "@vue/composition-api";
280
281
const searchTerm = ref("");
282
283
// Debounced search
284
watch(
285
searchTerm,
286
(newTerm, oldTerm, onInvalidate) => {
287
const timeout = setTimeout(() => {
288
console.log("Searching for:", newTerm);
289
// Perform search API call
290
}, 300);
291
292
// Cleanup previous timeout
293
onInvalidate(() => {
294
clearTimeout(timeout);
295
});
296
}
297
);
298
```
299
300
**Watching Getters with Dependencies:**
301
302
```typescript
303
import { ref, watch } from "@vue/composition-api";
304
305
const user = ref({ id: 1, name: "Alice" });
306
const posts = ref<any[]>([]);
307
308
// Watch a computed getter
309
watch(
310
() => user.value.id,
311
async (userId, prevUserId, onInvalidate) => {
312
const controller = new AbortController();
313
onInvalidate(() => controller.abort());
314
315
try {
316
const response = await fetch(`/api/users/${userId}/posts`, {
317
signal: controller.signal,
318
});
319
posts.value = await response.json();
320
} catch (error) {
321
if (error.name !== "AbortError") {
322
console.error("Failed to fetch posts:", error);
323
}
324
}
325
},
326
{ immediate: true }
327
);
328
```
329
330
### Watch in Component Setup
331
332
Typical usage patterns within Vue component setup functions with automatic cleanup.
333
334
```typescript
335
import { defineComponent, ref, watch, onUnmounted } from "@vue/composition-api";
336
337
export default defineComponent({
338
setup() {
339
const query = ref("");
340
const results = ref([]);
341
const loading = ref(false);
342
343
// Watchers are automatically cleaned up when component unmounts
344
watch(
345
query,
346
async (newQuery, oldQuery, onInvalidate) => {
347
if (!newQuery.trim()) {
348
results.value = [];
349
return;
350
}
351
352
loading.value = true;
353
const controller = new AbortController();
354
onInvalidate(() => {
355
controller.abort();
356
loading.value = false;
357
});
358
359
try {
360
const response = await fetch(`/api/search?q=${encodeURIComponent(newQuery)}`, {
361
signal: controller.signal,
362
});
363
const data = await response.json();
364
results.value = data.results;
365
} catch (error) {
366
if (error.name !== "AbortError") {
367
console.error("Search failed:", error);
368
results.value = [];
369
}
370
} finally {
371
loading.value = false;
372
}
373
},
374
{ immediate: false }
375
);
376
377
return {
378
query,
379
results,
380
loading,
381
};
382
},
383
});
384
```
385
386
## Types
387
388
```typescript { .api }
389
type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T);
390
391
type WatchCallback<V = any, OV = any> = (
392
value: V,
393
oldValue: OV,
394
onInvalidate: InvalidateCbRegistrator
395
) => any;
396
397
type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void;
398
399
type InvalidateCbRegistrator = (fn: () => void) => void;
400
401
type WatchStopHandle = () => void;
402
403
type MapSources<T, Immediate> = {
404
[K in keyof T]: T[K] extends WatchSource<infer V>
405
? Immediate extends true
406
? V | undefined
407
: V
408
: never;
409
};
410
411
type MultiWatchSources = (WatchSource<unknown> | object)[];
412
413
interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
414
immediate?: Immediate;
415
deep?: boolean;
416
}
417
418
interface WatchOptionsBase {
419
flush?: FlushMode;
420
}
421
422
type FlushMode = "pre" | "post" | "sync";
423
424
interface VueWatcher {
425
lazy: boolean;
426
get(): any;
427
teardown(): void;
428
update(): void;
429
run(): void;
430
evaluate(): void;
431
depend(): void;
432
}
433
```