React vs. Vue: comparisons
Choosing between React and Vue often comes down to how you want to build and reason about UI. React centers everything around JavaScript with JSX, explicit state hooks like useState and useReducer, and a Virtual DOM diffing process. Vue, on the other hand, leans into a template-and-directives style with built-in reactivity and conveniences like v-model for two-way binding.
In this post, I will break down the core features of each framework and compare how they handle templating, state, data flow, and rendering performance.
Features of React
JSX syntax: write HTML-like code directly in JavaScript files
JSX is actually a syntactic sugar: it compiles down to
React.createElement()function calls that create JavaScript objects representing DOM elements.const vDom = <h1>Hello World</h1>; // This translates to: const vDom = React.createElement( 'h1', { className: 'hClass', id: 'hId' }, 'Hello World' );
Component: a function or a class that returns JSX
function Greeting({ name }) { return <h1>Hello, {name}!</h1> } function App() { return <Greeting name="World" /> }
class Greeting extends React.Component { constructor(props) { super(props); this.state = {}; } render() { return <h1>Hello, {this.props.name}!</h1> } } class App extends React.Component { render() { return <Greeting name="World" /> } }
State management: React uses hooks like
useStateanduseReducerto manage component stateIn React, components need a way to store and update data that affects what gets displayed. React provides hooks (special functions) to do this:
- useState: Used for simple state values. For example, if you want to track a counter, you'd write
const [count, setCount] = useState(0). When you callsetCount(5), React updates the value and re-renders your component. - useReducer: Used for more complex state logic, especially when state updates depend on previous values or involve multiple related pieces of data. It works similarly to how Redux manages state.
React requires you to explicitly use these hooks to make your data reactive.
- useState: Used for simple state values. For example, if you want to track a counter, you'd write
Virtual DOM: React uses a virtual DOM to efficiently update the UI by comparing changes and only updating what's necessary
The virtual DOM is a programming concept where React keeps a lightweight copy of the actual DOM (Document Object Model) in memory. Here's how it works:
How it improves efficiency:
- When your data changes, React first updates the virtual DOM instead of directly manipulating the real DOM
- React then compares the new virtual DOM with the previous version to identify exactly what changed (this process is called "diffing")
- Finally, React updates only the specific parts of the real DOM that actually changed, rather than re-rendering everything
Why this matters: Virtual DOM does not perform layout and repaint operations
Direct DOM manipulation is slow and expensive in terms of performance. By using a virtual DOM to batch changes and minimize actual DOM updates, React makes UI updates much faster and more efficient.
Downsides:
- The virtual DOM adds memory overhead since React must maintain a copy of the DOM tree in memory
- When rendering a large amount of DOM components for the first time, the process is slightly slower than normal due to the additional layer of virtual DOM calculation.
- The virtual DOM abstraction can make it harder to optimize performance for specific edge cases where you know exactly what needs to update
Comparison with Vue:
Vue uses a different optimization approach—Vue's reactivity system tracks dependencies at the component level and only re-renders affected components, while React compares the entire component tree using this virtual DOM diffing algorithm.
Props: data passed from parent to child components, flowing in one direction
In React, props (short for "properties") are how you pass data from a parent component down to its child components. The key characteristic is that this data flow is unidirectional—it only goes one way, from parent to child.
For example:
function ParentComponent() { return <ChildComponent name="Alice" age={25} /> } function ChildComponent({ name, age }) { return <p>Hello, {name}! You are {age} years old.</p> }
Here, the parent passes
nameandageas props to the child. The child can read these values but cannot directly modify them. If the child needs to update data, it must communicate back to the parent (typically through callback functions passed as props), and the parent updates its state, which then flows back down as new props.This contrasts with Vue's two-way data binding approach, where data can automatically sync in both directions.
Features of Vue
Directives: special HTML attributes that provide reactive functionality to templates
v-bind(property binding):<img v-bind:src="imageUrl" v-bind:alt="imageDescription"> <!-- Shorthand --> <img :src="imageUrl" :alt="imageDescription">
v-on(event handler):<button v-on:click="handleClick">Click me</button> <!-- Shorthand --> <button @click="handleClick">Click me</button>
v-model(bidirectional data binding):<input v-model="username" type="text"> <!-- The username variable updates as you type, and changes to username update the input -->
v-if(conditional rendering):<div v-if="isLoggedIn">Welcome back!</div> <div v-else>Please log in</div>
v-for(list rendering):<ul> <li v-for="item in items" :key="item.id">{{ item.name }}</li> </ul>
v-show(conditional display):<div v-show="isVisible">This element is hidden with CSS display: none</div> <!-- Unlike v-if, v-show always renders the element but toggles visibility -->
v-textandv-html(content rendering):<span v-text="message"></span> <!-- Renders text content safely --> <div v-html="htmlContent"></div> <!-- Renders raw HTML (use with caution) -->
Reactivity: Vue's reactive data system automatically updates the DOM when data changes
Vue tracks which component code depends on which reactive values. When a reactive value changes, Vue can update only the affected components instead of re-rendering the whole tree.
A common pattern is to keep state in a
refand derive values withcomputed:import { ref, computed } from 'vue' const count = ref(0) const doubled = computed(() => count.value * 2)
ref: Creates a reactive reference to a value. Here,countis a reactive variable initialized to 0. To access or modify its value, you usecount.value.computed: Creates a reactive computed property that automatically updates when its dependencies change. In this example,doubledwill always be twice the value ofcount, and it updates automatically whenevercountchanges.
Component-based: build encapsulated components that manage their own state
Vue apps are built from components (Single-File Components or plain component objects). Each component can keep its own local state (with
ref/reactive), receive inputs from a parent viaprops, and communicate upward withemitevents.For example, this component owns its own
countstate and updates it on click:<script setup> import { ref } from 'vue' const count = ref(0) </script> <template> <button @click="count++">Clicked {{ count }} times</button> </template>
Comparisons
React uses JSX syntax while Vue uses template syntax with directives
This means that React and Vue use different approaches for writing component templates:
React with JSX: You write HTML-like syntax directly within JavaScript code. For example:
function Component() { return <div className="container">Hello</div> }
Vue with Template Syntax: You write HTML templates with special directives (attributes starting with
v-). For example:<template> <div v-if="show" v-bind:class="containerClass">Hello</div> </template>
Vue's directives like
v-if,v-for, andv-bindprovide declarative ways to handle logic in templates, while React embeds JavaScript expressions directly using curly braces in JSX.
Vue has built-in state management (reactive data) while React requires
useState/useReducerhooksVue has reactivity built into its core framework—when you create data properties in a Vue component, they automatically become reactive and trigger UI updates when changed.
In contrast, React doesn't have built-in reactivity. Instead, you need to explicitly use hooks like
useStateoruseReducerto manage state and trigger re-renders when data changes.For example:
- Vue: You can simply define
data() { return { count: 0 } }and Vue automatically tracks changes - React: You must write
const [count, setCount] = useState(0)and usesetCount()to update the value
Still, shared app-wide state is a design choice. You might still want to create a dedicated store, or use module-level reactive singletons.
- Vue: You can simply define
Vue provides two-way data binding with v-model, React uses one-way data flow
This refers to how data synchronization works between the user interface and the underlying data model:
Vue's two-way data binding with v-model:
When you use
v-modelon an input field, changes flow in both directions automatically:- When the data changes in your code, the input field updates
- When the user types in the input field, the data in your code updates automatically
For example, with
<input v-model="username">, typing in the input instantly updates theusernamevariable without additional code.React's one-way data flow:
Data flows in only one direction. To achieve similar functionality, you need to:
- Pass the value to the input:
value={username} - Handle changes explicitly:
onChange={(e) => setUsername(e.target.value)}
This makes React more explicit but requires more code, while Vue's
v-modelprovides a convenient shorthand for common form input scenarios.
Vue uses a more optimized diff algorithm that tracks dependencies at the component level, while React compares the entire component tree
- Vue's dependency tracking: Vue's reactivity system tracks exactly which components depend on which pieces of data. When data changes, Vue knows precisely which components need to be updated and only re-renders those specific components. This is more granular and efficient because it operates at the component level with fine-grained dependency tracking.
- React's tree comparison: React uses a "virtual DOM" diffing algorithm that compares the entire component tree (or large portions of it) to determine what changed. When state updates, React re-renders the component and its children, then compares the new virtual DOM tree with the previous one to find differences. This can involve more computation since it examines broader parts of the component hierarchy.
In practice, this means Vue can be more efficient in scenarios with many components, as it avoids unnecessary checks by knowing exactly which components are affected by a data change, while React performs broader comparisons to detect changes.