Vue versus React
The comparison question is really a design question: can you explain two different solutions to the same problem, and pick one for stated reasons rather than taste. Answering “Vue is easier” or “React has a bigger ecosystem” is the weak version.
The one real difference
React re-runs your component and diffs the output. Vue tracks which values a component read and re-runs only affected effects.
Everything else follows from that.
| Consequence | React | Vue |
|---|---|---|
| Knowing what changed | diff the new tree against the old | the dependency was recorded when it was read |
| Dependency arrays | required for useEffect, useMemo, useCallback |
none |
| Manual memoization | was pervasive; React Compiler now automates it | never needed |
| Stale closures | the dominant class of hook bug | rare; refs are live boxes |
| Parent re-render | re-renders children unless memoized | does not, unless props actually changed |
| Mutating state | forbidden — identity is how change is detected | expected — the proxy detects the mutation |
That last row causes real confusion when switching:
// React: identity is the change signal, so this renders nothing.
const [items, setItems] = useState([])
items.push(x) // no re-render
setItems([...items, x]) // correct// Vue: the proxy observes the mutation.
const items = reactive([])
items.push(x) // correct, updatesSame line, opposite verdict. React compares references, so mutating in place
leaves the reference identical and nothing re-renders; Vue’s proxy intercepts
push itself, so a new array is unnecessary work.
The stale-closure counterpart is the other daily difference:
// React: `count` is captured at render time.
useEffect(() => {
const id = setInterval(() => setCount(count + 1), 1000)
return () => clearInterval(id)
}, []) // never sees an updated count// Vue: a ref is a live box, read at call time.
setInterval(() => count.value++, 1000)React’s answer is setCount(c => c + 1) or a correct dependency array. Vue does
not have the problem, because there is no closure over a value — only over the
box holding it.
Templates versus JSX
React’s JSX is JavaScript, so the full language is available and the compiler cannot know what is static. Vue’s templates are a restricted, analysable language, so the compiler hoists static nodes, tags dynamic ones with patch flags, and flattens the tree. That is why “Vue re-rendered” is cheaper than “React re-rendered”. See The Vue compiler and rendering pipeline.
The trade is expressiveness. Rendering logic that varies structurally is easier in JSX; Vue offers render functions and JSX for those cases at the cost of the optimisations.
Ecosystem and defaults
| React | Vue | |
|---|---|---|
| Router | choose (React Router, TanStack Router, framework built-in) | Vue Router, official |
| State | choose (Redux, Zustand, Jotai, context) | Pinia, official |
| Meta-framework | Next.js, React Router framework mode, Remix | Nuxt, official |
| Build | Vite or the framework’s | Vite (same author as Vue) |
| Styling | choose | scoped styles in the SFC, built in |
React gives you decisions; Vue gives you defaults. On a team that has already made those decisions well, React’s flexibility is an advantage. On a team that has not, Vue’s defaults prevent a category of argument — and mean any Vue codebase is recognisable to any Vue developer.
API surface comparison
| Concept | React | Vue |
|---|---|---|
| Local state | useState |
ref / reactive |
| Derived value | useMemo (or the compiler) |
computed |
| Side effect | useEffect + deps |
watchEffect / watch |
| DOM handle | useRef |
template ref + useTemplateRef |
| Context | useContext |
provide / inject |
| Logic reuse | custom hook | composable |
| Slot / children | children, render props |
<slot>, scoped slots |
| Portal | createPortal |
<Teleport> |
| Lazy + fallback | React.lazy + <Suspense> |
defineAsyncComponent + <Suspense> |
| Two-way binding | none — controlled inputs by hand | v-model |
Where each is genuinely stronger
React: the largest ecosystem and hiring pool; React Native for mobile with shared knowledge; Server Components are further along in production use; more third-party components exist for any given niche.
Vue: less boilerplate for the same app; the compiler removes a class of performance work; SFCs keep template, logic and scoped styles together; official router and store mean fewer architectural decisions; the learning curve to productive is shorter.
Both are converging: React added a compiler to automate what Vue’s reactivity always did; Vue’s Vapor Mode removes the virtual DOM that React still relies on.
Interview angle 5
- “Vue or React — which would you choose?” - answer with a constraint, not a preference. Existing team knowledge, whether React Native is needed, whether the org wants defaults or flexibility, hiring in your market. Either is a defensible choice for almost any web app, and saying so is the senior answer.
- “Why does Vue not need
useMemoand dependency arrays?” - fine-grained reactivity. Vue records which reactive values were read, so it knows exactly what to re-run. React re-runs the component wholesale and needs you to tell it what changed. - “What is the hardest thing when moving from React to Vue?” - unlearning immutability as the update mechanism. In Vue you mutate reactive state directly, and reaching for a new object every time is unnecessary. In the other direction, it is dependency arrays and stale closures.
- “Is one faster?” - for typical apps, both are fast enough and the bottleneck is your data fetching and bundle size. Vue’s compiler does less work per update by design; React’s compiler narrowed that gap. Answering with a benchmark number rather than “it depends on what you build” is the wrong instinct.
- “How do the ecosystems differ practically?” - React is a library where you assemble the stack; Vue ships an official router, store and meta-framework. That is a team-maturity question more than a technical one.