跪拜 Guibai
← All articles
JavaScript · Programming Language · Programmer

Vue's nextTick Waits for the Update Queue, Not the Render: Five Pitfalls That Break Your Charts

By 挖掘狂人 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Mistaking nextTick for a render callback silently breaks chart sizing, scroll math, and SSR guards. The fix is to treat nextTick as a microtask boundary and let rendering-aware APIs like ResizeObserver and flush:'post' handle the rest.

Summary

Vue batches component updates into a single queue and flushes it in a microtask. nextTick schedules a callback after that flush, so it guarantees the virtual DOM is now the real DOM. It gives no guarantee about style computation, layout, or paint; a container can exist with offsetWidth 0. That's why the classic modal-with-a-chart pattern fails: the DOM is in place, but the browser hasn't run layout when nextTick fires.

The same misunderstanding crops up in five common scenarios. Initializing charts after a modal or tab switch needs a requestAnimationFrame wrapper or a ResizeObserver subscriber. Calculating scroll height after list updates requires waiting on image load events and document.fonts.ready. Async child components don't respond to a parent's nextTick; they need Suspense or an explicit emit from onMounted. Mutating reactive data inside a nextTick callback can trigger an endless update loop, and SSR environments have no DOM at all, so DOM code must live in onMounted. Tests also need flushPromises to drain the update queue.

Vue 3.5's declarative alternatives eliminate most manual timing: useTemplateRef for typed refs, watch with flush:'post' to run callbacks after DOM updates, Suspense for async children, and ResizeObserver for size-aware logic. nextTick remains a microtask boundary; it will never wait for browser rendering, and Vue 3.6's Vapor mode won't change that.

Takeaways
— nextTick resolves after Vue's async update queue flushes; it does not wait for browser layout or paint.
— Container dimensions can be 0 even when the DOM node exists, because layout hasn't run.
— For chart initialization in modals or tabs, use requestAnimationFrame after nextTick or ResizeObserver on the container.
— Scroll height calculations after list updates need image load events and document.fonts.ready, not just nextTick.
— Async child components don't wait for nextTick in the parent; use Suspense or have the child emit an event when mounted.
— Mutating reactive data inside a nextTick callback can trigger another update cycle; add guards or split steps.
— SSR has no DOM, so DOM-dependent code must run in onMounted. Tests should use flushPromises to drain the update queue.
Conclusions

nextTick's guarantee is deliberately narrow; the confusion comes from treating it as a universal 'after DOM' hook when it only covers Vue's own patching, not the browser's rendering pipeline.

Because browser layout and paint happen after microtasks, any measurement of element geometry right after nextTick is a lottery. Using ResizeObserver shifts the responsibility to the browser, which is the only thing that knows the real dimensions.

Vue's direction toward declarative scheduling (watch with flush:'post', Suspense) suggests that imperative waiting is a code smell; most nextTick uses can be expressed as reactive dependencies.

Concepts & terms
flush: 'post'
A watch option that runs the callback after the DOM update, letting you react to data changes with the rendered DOM available. It's a declarative replacement for nextTick and onUpdated.
Vapor mode
An upcoming rendering mode in Vue 3.6 that compiles to more fine-grained updates, improving render performance while leaving nextTick's semantics unchanged.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗