DevelopmentAll levels

Vue

Vue is a framework built around a reactivity system that watches what your code reads. You do not declare dependencies and you do not tell it what changed — reading a value during a render registers that render as a dependant, and writing the value re-runs exactly those dependants. Most of what distinguishes Vue in practice follows from that one mechanism.

Reactivity
Proxy-based, tracked
Unit of code
Single-file component
Two APIs
Composition · Options

Reading is what creates the dependency

Vue wraps your state in a proxy — an object that can observe every read and every write of its properties. That single capability is the whole system, and everything else is a consequence of it.

Vue wraps state in a proxy. While an effect runs — a render, a computed, a watcher — every property it reads records that effect as a dependency; that is tracking. When a property is written, only the effects that recorded it re-run; that is triggering. Nothing else is checked, which is why Vue needs no dependency array, and why a value read outside a reactive effect is invisible to the system.

While an effect is running — a component render, a computed value, a watcher — Vue knows which effect is current. Every property read during that run records the effect against itself. That is tracking. Later, when a property is written, Vue re-runs the effects recorded against that specific property, and nothing else. That is triggering.

The cost shows up as one specific trap. Reactivity lives on the proxy, not on the values inside it, so pulling a value out breaks the connection: const { count } = props copies a number, and the copy will never update. This is why ref exists — it boxes a value in an object with a .value property, so passing the box around keeps reactivity intact. toRefs does the same for an object you want to destructure.

The .value suffix is the part newcomers dislike, and it is worth understanding rather than resenting: it is the visible seam where a plain value becomes a tracked one. In templates Vue unwraps refs automatically, so it only appears in script, and in single-file components a compiler transform removes most of it too.

Single-file components and two APIs

A Vue component is normally one .vue file holding three blocks: the template, the script and the styles. The argument for keeping them together is that they change together — editing a component means editing all three — and the argument against, that it mixes languages in one file, has been steadily losing for a decade.

The styles block accepts a scoped attribute, which is a genuinely useful default: Vue adds a generated attribute to the elements of that component and scopes every selector to it, so a rule cannot escape into another component. That is the locality property CSS methodologies are usually adopted to provide, supplied by the compiler for free.

Composition API

  • Code grouped by feature, not by option type
  • Logic extracts into plain functions — composables
  • Types infer naturally, so TypeScript needs no help
  • The default for new code

Options API

  • Code grouped into data, computed, methods, watch
  • Reuse needs mixins, whose origins get hard to trace
  • Easier first hour; harder tenth component
  • Supported, common in existing code

The difference matters at size rather than at the start. In a component with three concerns, the Options API scatters each one across four sections, so understanding "how does search work here" means reading four places and holding them together; the Composition API lets the three concerns sit as three blocks, and lets any of them move out into a composable that another component can import. Both are supported; new code should use Composition.

Computed or watch

This is the decision Vue developers get wrong most often, and it has a clean rule. A computed derives a value: it is cached, it recalculates only when something it read changes, and it must be free of side effects. A watch reacts to a change by doing something: calling an API, writing to storage, starting an animation.

The test is one question: are you producing a value or performing an action? A watcher that ends by assigning to another piece of state is almost always a computed written the long way — it runs one tick late, it can loop if two watchers write to each other's sources, and it makes the relationship between the two values invisible in the code.

The same rule extends across the framework. v-if removes an element from the DOM and v-show toggles its CSS display, so v-if costs on every toggle and v-show costs once on mount — the choice is about how often it flips. And v-for needs a :key for the same reason React does: without a stable identity, Vue reuses DOM nodes for the wrong items and state ends up attached to the wrong row.

What comes in the box

Vue sits between React and Angular on the amount it decides for you. It is not a library that leaves everything open, and it is not a framework that supplies the whole application architecture. It ships a small number of official packages, and because they are official the ecosystem converges on them rather than splintering.

  • Vue Router — the official router, so a Vue project does not begin with a routing comparison.
  • Pinia — the official store, and small enough that using it for a single shared value is not overkill.
  • Nuxt — the meta-framework for routing conventions, server rendering, data fetching and deployment.
  • The reactivity system itself is published separately, and can be used with no components at all.

The last one is worth a moment. @vue/reactivity is a standalone package: refs, computed values and effects with no rendering attached. It is a fair signal about where the framework's centre of gravity is — the component layer is built on the reactivity system, not the other way around.

Vue against React

QuestionVueReact
How are dependencies known?Discovered by readingDeclared, checked by lint
What re-runs on a change?Only the effects that read itThe component and its subtree
How is the view written?A template the compiler analysesJSX — ordinary JavaScript
Who decides the architecture?Official router and storeYou do, every time
Where does state get lost?Destructuring a reactive objectMutating instead of replacing

Neither column is a verdict. The template approach lets the compiler know more — it can see which parts of the view are static and skip them entirely, which is work React has to do at runtime — while JSX is just JavaScript, so anything you can express in the language you can express in the view. Choose on the two things that actually differ in a team: whether you want conventions supplied, and which pool of experienced developers you can hire from.

How this shows up in real delivery

Vue's reputation for being approachable is earned and slightly misleading. The first day is genuinely easier: a template looks like HTML, the reactivity does what you expect, and there is no dependency array to get wrong. The part that requires care arrives later and is specific — knowing exactly where reactivity is preserved and where it is lost, because the failure is silent. Nothing throws when you destructure a reactive object; the value simply stops updating, and the bug looks like a rendering problem.

Where it degrades

  • Destructuring a reactive object or props, which silently detaches the value from the system.
  • A watcher that assigns to other state, which is a computed running one tick late.
  • Mixing Composition and Options APIs inside one component for no stated reason.
  • v-if and v-for on the same element, where the precedence is not what most people assume.
  • Mutating a prop from a child, which breaks the one-way flow the framework relies on.
  • provide / inject used as a global variable, which hides where a value comes from.
  • Deeply reactive large lists, where the proxy work is real and shallowRef was the answer.

When to use it

Use it when

  • You want official answers for routing and state rather than a comparison exercise.
  • The team includes people who write more markup than JavaScript — templates fit that shape.
  • Scoped styles by default would remove a CSS methodology you would otherwise adopt.
  • You want fine-grained updates without hand-tuning what re-renders.

Avoid it when

  • The local hiring market is overwhelmingly React and the team will change hands.
  • The project needs a library ecosystem that only exists for React today.
  • A page with almost no state — reactive machinery is overhead you would never use.
  • Nobody will learn where reactivity is lost, which makes the silent failures hard to support.

Found this useful?

Share it with someone who is working on the same problem.