0
# Scheduler & Timing
1
2
Vue's scheduler provides utilities for controlling the timing of updates and DOM operations, enabling precise control over when code executes relative to Vue's update cycle.
3
4
## Capabilities
5
6
### Next Tick Scheduling
7
8
Schedule callbacks to run after the next DOM update cycle.
9
10
```typescript { .api }
11
/**
12
* Defers callback execution until after the next DOM update cycle
13
* @param fn - Optional callback function to execute
14
* @returns Promise that resolves after the next tick
15
*/
16
function nextTick(fn?: () => void): Promise<void>;
17
```
18
19
**Usage Examples:**
20
21
```typescript
22
import { ref, nextTick } from "@vue/runtime-core";
23
24
const MyComponent = defineComponent({
25
setup() {
26
const count = ref(0);
27
const elementRef = useTemplateRef<HTMLElement>('element');
28
29
const updateAndRead = async () => {
30
// Update reactive data
31
count.value++;
32
33
// Wait for DOM to update
34
await nextTick();
35
36
// Now DOM reflects the new count
37
if (elementRef.value) {
38
console.log('Updated DOM content:', elementRef.value.textContent);
39
console.log('DOM height:', elementRef.value.offsetHeight);
40
}
41
};
42
43
// With callback style
44
const updateWithCallback = () => {
45
count.value++;
46
47
nextTick(() => {
48
console.log('DOM updated with new count:', count.value);
49
});
50
};
51
52
// Multiple nextTick calls
53
const chainedUpdates = async () => {
54
count.value = 1;
55
await nextTick();
56
console.log('First update complete');
57
58
count.value = 2;
59
await nextTick();
60
console.log('Second update complete');
61
};
62
63
return {
64
count,
65
elementRef,
66
updateAndRead,
67
updateWithCallback,
68
chainedUpdates
69
};
70
}
71
});
72
73
// Usage in lifecycle hooks
74
const LifecycleExample = defineComponent({
75
setup() {
76
const data = ref('initial');
77
78
onMounted(async () => {
79
// Change data
80
data.value = 'mounted';
81
82
// Wait for DOM update
83
await nextTick();
84
85
// Now safe to access updated DOM
86
console.log('DOM updated after mount');
87
});
88
89
return { data };
90
}
91
});
92
93
// Error handling with nextTick
94
const ErrorHandlingExample = defineComponent({
95
setup() {
96
const handleUpdate = async () => {
97
try {
98
// Some reactive updates
99
// ...
100
101
await nextTick();
102
103
// DOM operations that might fail
104
const element = document.querySelector('.my-element');
105
if (element) {
106
element.scrollIntoView();
107
}
108
} catch (error) {
109
console.error('Error after nextTick:', error);
110
}
111
};
112
113
return { handleUpdate };
114
}
115
});
116
```
117
118
### Post-flush Callback Queue
119
120
Queue callbacks to run after all component updates have been flushed.
121
122
```typescript { .api }
123
/**
124
* Queues a callback to run after all pending updates have been flushed
125
* @param cb - Callback function to queue
126
*/
127
function queuePostFlushCb(cb: SchedulerJob): void;
128
129
interface SchedulerJob {
130
(): void;
131
id?: number;
132
pre?: boolean;
133
active?: boolean;
134
computed?: boolean;
135
}
136
```
137
138
**Usage Examples:**
139
140
```typescript
141
import { ref, queuePostFlushCb } from "@vue/runtime-core";
142
143
const PostFlushExample = defineComponent({
144
setup() {
145
const list = ref([1, 2, 3]);
146
147
const addItems = () => {
148
// Update the list
149
list.value.push(4, 5, 6);
150
151
// Queue callback to run after all updates
152
queuePostFlushCb(() => {
153
console.log('All updates flushed, list length:', list.value.length);
154
// Safe to access updated DOM here
155
const listElement = document.querySelector('.list');
156
if (listElement) {
157
console.log('List DOM children:', listElement.children.length);
158
}
159
});
160
};
161
162
// Multiple post-flush callbacks
163
const multipleCallbacks = () => {
164
list.value = [10, 20, 30];
165
166
queuePostFlushCb(() => {
167
console.log('First post-flush callback');
168
});
169
170
queuePostFlushCb(() => {
171
console.log('Second post-flush callback');
172
});
173
};
174
175
// Callback with job properties
176
const advancedCallback = () => {
177
list.value.reverse();
178
179
const job: SchedulerJob = () => {
180
console.log('Advanced post-flush job executed');
181
};
182
job.id = 1;
183
184
queuePostFlushCb(job);
185
};
186
187
return {
188
list,
189
addItems,
190
multipleCallbacks,
191
advancedCallback
192
};
193
}
194
});
195
196
// Integration with watchers
197
const WatcherIntegration = defineComponent({
198
setup() {
199
const count = ref(0);
200
const computedDouble = computed(() => count.value * 2);
201
202
watch(count, (newCount) => {
203
console.log('Watcher fired for count:', newCount);
204
205
// Queue post-flush callback
206
queuePostFlushCb(() => {
207
console.log('Post-flush: computed value is', computedDouble.value);
208
});
209
});
210
211
return {
212
count,
213
computedDouble,
214
increment: () => count.value++
215
};
216
}
217
});
218
```
219
220
### Advanced Timing Patterns
221
222
Common patterns for precise timing control in Vue applications.
223
224
```typescript
225
// Debounced updates with nextTick
226
function useDebounceWithNextTick<T extends (...args: any[]) => any>(
227
fn: T,
228
delay: number
229
): T {
230
let timeoutId: NodeJS.Timeout;
231
232
return ((...args: Parameters<T>) => {
233
clearTimeout(timeoutId);
234
timeoutId = setTimeout(async () => {
235
await nextTick();
236
fn(...args);
237
}, delay);
238
}) as T;
239
}
240
241
// Batch DOM reads after updates
242
class DOMBatchReader {
243
private callbacks: (() => void)[] = [];
244
private scheduled = false;
245
246
read(callback: () => void) {
247
this.callbacks.push(callback);
248
if (!this.scheduled) {
249
this.scheduled = true;
250
nextTick(() => {
251
const toExecute = [...this.callbacks];
252
this.callbacks.length = 0;
253
this.scheduled = false;
254
255
toExecute.forEach(cb => cb());
256
});
257
}
258
}
259
}
260
261
const batchReader = new DOMBatchReader();
262
263
const BatchReadingExample = defineComponent({
264
setup() {
265
const height = ref(0);
266
const width = ref(0);
267
const elementRef = useTemplateRef<HTMLElement>('element');
268
269
const measureElement = () => {
270
batchReader.read(() => {
271
if (elementRef.value) {
272
height.value = elementRef.value.offsetHeight;
273
width.value = elementRef.value.offsetWidth;
274
}
275
});
276
};
277
278
return {
279
height,
280
width,
281
elementRef,
282
measureElement
283
};
284
}
285
});
286
287
// Coordinated updates across components
288
const useCoordinatedUpdate = () => {
289
const pendingUpdates = new Set<() => void>();
290
291
const scheduleUpdate = (updateFn: () => void) => {
292
pendingUpdates.add(updateFn);
293
294
nextTick(() => {
295
queuePostFlushCb(() => {
296
pendingUpdates.forEach(fn => fn());
297
pendingUpdates.clear();
298
});
299
});
300
};
301
302
return { scheduleUpdate };
303
};
304
305
// Animation frame coordination
306
const useAnimationTiming = () => {
307
const requestNextFrame = (callback: () => void) => {
308
nextTick(() => {
309
requestAnimationFrame(callback);
310
});
311
};
312
313
const requestDoubleFrame = (callback: () => void) => {
314
requestNextFrame(() => {
315
requestAnimationFrame(callback);
316
});
317
};
318
319
return {
320
requestNextFrame,
321
requestDoubleFrame
322
};
323
};
324
```
325
326
## Timing Guarantees
327
328
### nextTick Execution Order
329
330
1. **Synchronous updates**: All synchronous reactive updates are batched
331
2. **nextTick callbacks**: Queued callbacks execute after DOM updates
332
3. **Post-flush callbacks**: `queuePostFlushCb` callbacks execute after nextTick
333
4. **Browser paint**: Browser repaints after all Vue updates complete
334
335
### Best Practices
336
337
1. **DOM Measurements**: Always use `nextTick` before measuring DOM elements
338
2. **Third-party Integration**: Use `nextTick` when integrating with libraries that need updated DOM
339
3. **Animation Triggers**: Combine with `requestAnimationFrame` for smooth animations
340
4. **Error Boundaries**: Wrap DOM operations in try-catch after `nextTick`
341
342
### Performance Considerations
343
344
```typescript
345
// Good: Batch multiple updates
346
const batchUpdates = async () => {
347
item1.value = 'new value 1';
348
item2.value = 'new value 2';
349
item3.value = 'new value 3';
350
351
// Single nextTick for all updates
352
await nextTick();
353
354
measureAllElements();
355
};
356
357
// Avoid: Multiple nextTick calls for same update cycle
358
const inefficientUpdates = async () => {
359
item1.value = 'new value 1';
360
await nextTick(); // Unnecessary
361
362
item2.value = 'new value 2';
363
await nextTick(); // Unnecessary
364
365
item3.value = 'new value 3';
366
await nextTick(); // Only this one needed
367
};
368
```
369
370
## Types
371
372
```typescript { .api }
373
interface SchedulerJob {
374
(): void;
375
/**
376
* Job ID for sorting/deduplication
377
*/
378
id?: number;
379
/**
380
* Whether this is a pre-flush job
381
*/
382
pre?: boolean;
383
/**
384
* Whether the job is currently active
385
*/
386
active?: boolean;
387
/**
388
* Whether this job is from a computed
389
*/
390
computed?: boolean;
391
}
392
393
/**
394
* Function signature for nextTick
395
*/
396
interface NextTickFunction {
397
(): Promise<void>;
398
(fn: () => void): Promise<void>;
399
}
400
401
/**
402
* Function signature for queuePostFlushCb
403
*/
404
interface QueuePostFlushCbFunction {
405
(cb: SchedulerJob): void;
406
}
407
```