Contents
In brief
Vue 3 with Proxy reactivity, Composition API, and TypeScript looks simpler than Vue 2 — until production teaches the difference between “can use” and “use correctly.” A Dev.to roundup covers reactivity traps, ref vs reactive, watchers, and newer pieces like Teleport and Suspense.
What happened
Reactivity moved from Object.defineProperty to Proxy, fixing Vue 2 pain points (new object keys, array indices). The trade-off is a new mental model: where does the DOM update link break when you destructure or replace whole objects?
Rule of thumb from the article: ref for primitives and objects you might replace entirely; reactive for nested structures like forms. The trap: reactive returns a proxy — let { count } = state gives a plain number; incrementing it won’t re-render. Fix with toRefs so each field stays a ref wrapper.
Ref unwrapping differs by container: template auto-unwraps, reactive arrays unwrap refs inside them, plain arrays do not — you need .value. Replacing a whole reactive object (data = { items: [...] }) drops reactivity; use ref + data.value = … or Object.assign on the existing proxy.
In Composition API, watchEffect runs immediately and tracks deps — great for logging, dangerous if you mutate those same refs (infinite loop). For controlled old/new comparisons on a specific source, prefer watch.
Why it matters
Teams pick Vue 3 as “lighter React,” but many production bugs are broken reactivity chains: composables returning .value instead of refs, lifecycle hooks registered inside setTimeout, scoped styles missing Teleport content. Linters rarely catch these; SSR exposes them late.
You need an explicit composable contract — expose refs/reactive, not bare values — and review destructuring. TypeScript helps only with disciplined defineProps<{…}>() + withDefaults, not mixed runtime/type declarations.
In practice
- Destructuring
reactive? Wrap withtoRefsor lose reactivity. - Data may be fully replaced? Use
ref, notreactive. - Don’t mutate deps blindly inside
watchEffect; usewatchfor old/new. - Composables return
{ x, y }as refs, not unwrapped values. - Register
onMountedetc. synchronously in setup, not from async callbacks. - Teleport moves DOM, not component context — use
:deep()or global CSS. - Treat Suspense + async components carefully in SSR and critical routes.
Takeaway
Vue 3’s difficulty isn’t syntax — it’s the reactivity model. The Dev.to post is a solid checklist before migrating to Composition API: ref/reactive, watchers, composable contracts, Teleport edge cases. See the original for code samples.