There is no doubt that ReactJs is still the leading and most used JavaScript framework. However, I am seeing more and more React developers moving to Vue and enjoying it. Likewise, Vue developers are trying out React and finding it very similar. I believe that since the release of Vite and Vue/Nuxt 3, along with the composition API and other changes, the gap between the two frameworks is narrowing.
React components are JavaScript functions that return markup:
function MyButton() {
return (
<button>I'm a button</button>
);
}
Using the component in in the same file (App.js):
function MyButton() {
return (
<button>
I'm a button
</button>
);
}
export default function MyApp() {
return (
<div>
<h1>Welcome to my app</h1>
<MyButton />
</div>
);
}
React projects use JSX. JSX is stricter than HTML. In React:
<div> or a <>...</>:// ❌ Not allowed
return <h1>Hello</h1><p>World</p>;
// ✅ Allowed
return (
<div>
<h1>Hello</h1>
<p>World</p>
</div>
);
In React, it's common to split components into different files. You can then import them and use them as children, passing data via props.
Example: Button component in a separate file (MyButton.js):
// MyButton.js
export default function MyButton({ label, onClick }) {
return (
<button onClick={onClick}>
{label}
</button>
);
}
Using the Button component in a parent (App.js):
// App.js
import MyButton from './MyButton';
export default function MyApp() {
const handleClick = () => {
alert('Button clicked!');
};
return (
<div>
<h1>Welcome to my app</h1>
<MyButton label="Click me" onClick={handleClick} />
</div>
);
}
So far, we have covered:
Let's dive deeper with a few more concepts. Then we will move the Vue.
React uses the useState hook to manage local component state. Updating state triggers a re-render of the component.
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
return (
<div>
<p>Current count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
Another example:
import { useState } from 'react';
export default function Greeting() {
const [name, setName] = useState('');
return (
<div>
<input
type="text"
placeholder="Enter your name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<p>Hi, {name || 'there'}!</p>
</div>
);
}
React has a one-way data flow: data moves from parent to child via props. When state changes, React re-renders the component.
function Display({ message }) {
return <p>{message}</p>;
}
export default function App() {
const [text, setText] = useState('Hello!');
return (
<div>
<Display message={text} />
<button onClick={() => setText('Hello, React!')}>Change Text</button>
</div>
);
}
If you observe above you might notice setText. You might be wondering what the heck is this.
This is a state updater function returned by React’s useState.
useState is a React hook for creating state in functional components. It returns an array with two items:
const [count, setCount] = useState(0);
const [name, setName] = useState('');
In React, there is no special syntax for writing conditions. Instead, you’ll use the same techniques as you use when writing regular JavaScript code. For example, you can use an if statement to conditionally include JSX:
let content;
if (isLoggedIn) {
content = <AdminPanel />;
} else {
content = <LoginForm />;
}
return (
<div>
{content}
</div>
);
If you observe above, see how cool is to assign to the variable let content directly your component either AdminPanel or LoginForm
Also, If you prefer more compact code, you can use the conditional ? operator. Unlike if, it works inside JSX:
<div>
{isLoggedIn ? (
<AdminPanel />
) : (
<LoginForm />
)}
</div>
For example, let’s say you have an array of products:
const products = [
{ title: 'Cabbage', id: 1 },
{ title: 'Garlic', id: 2 },
{ title: 'Apple', id: 3 },
];
const listItems = products.map(product =>
<li key={product.id}>
{product.title}
</li>
);
return (
<ul>{listItems}</ul>
);
Events in React are handled via JSX attributes, and you can pass functions as props to children.
function MyButton({ label, onClick }) {
return <button onClick={onClick}>{label}</button>;
}
export default function App() {
const handleClick = () => alert('Button clicked!');
return (
<div>
<MyButton label="Click Me" onClick={handleClick} />
</div>
);
}
Vue 3 components are typically written in single-file components (SFCs) with <template>, <script>, and <style>.
<!-- MyButton.vue -->
<template>
<button>I'm a button</button>
</template>
<script setup>
</script>
Using the component in a parent:
<!-- App.vue -->
<template>
<div>
<h1>Welcome to my Vue app</h1>
<MyButton />
</div>
</template>
<script setup>
import MyButton from './MyButton.vue';
</script>
Child (MyButton.vue):
<template>
<button @click="onClick">{{ label }}</button>
</template>
<script setup>
defineProps({
label: String,
onClick: Function,
});
</script>
Parent (App.vue):
<template>
<MyButton label="Click me" :onClick="handleClick" />
</template>
<script setup>
import MyButton from './MyButton.vue';
const handleClick = () => alert('Button clicked!');
</script>
<template>
<div>
<input v-model="name" placeholder="Enter your name" />
<p>Hi, {{ name || 'there' }}!</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
const name = ref('');
</script>
Child (DisplayMessage.vue):
<template>
<p>{{ message }}</p>
</template>
<script setup>
defineProps({
message: String
});
</script>
Parent (App.vue):
<template>
<DisplayMessage :message="text" />
<button @click="text = 'Hello, Vue!'">Change Text</button>
</template>
<script setup>
import { ref } from 'vue';
import DisplayMessage from './DisplayMessage.vue';
const text = ref('Hello!');
</script>
Use v-if / v-else with reactive variables.
<template>
<div>
<AdminPanel v-if="isLoggedIn" />
<LoginForm v-else />
</div>
</template>
<script setup>
import { ref } from 'vue';
import AdminPanel from './AdminPanel.vue';
import LoginForm from './LoginForm.vue';
const isLoggedIn = ref(false);
</script>
<template>
<ul>
<li v-for="product in products" :key="product.id">
{{ product.title }}
</li>
</ul>
</template>
<script setup>
const products = [
{ title: 'Cabbage', id: 1 },
{ title: 'Garlic', id: 2 },
{ title: 'Apple', id: 3 },
];
</script>
Events are passed as props; parent function is invoked in child. Works naturally with reactive data.
Child (MyButton.vue):
<template>
<button @click="onClick">{{ label }}</button>
</template>
<script setup>
defineProps({
label: String,
onClick: Function
});
</script>
<template>
<MyButton label="Click Me" :onClick="handleClick" />
</template>
<script setup>
import MyButton from './MyButton.vue';
const handleClick = () => alert('Button clicked!');
</script>
.vue single-file components with <template>, <script>, and <style>. Multiple root elements are allowed in templates.useState and useReducer for local state. Global state management can use Context API, Redux, or Zustand.reactive and ref for local state. Global state management can use Pinia or Vuex.v-model.useEffect, useMemo, useCallback) to reuse logic and manage side effects.setup() function) to organize reactive logic and reuse code via composables.onClick={handleClick}.v-on or @click and can be emitted to the parent using $emit.Conclusion:
Both frameworks are highly capable and share many concepts, like components, props, and a virtual DOM. React favors JSX and hooks, while Vue 3 emphasizes templates and the Composition API. Understanding these similarities and differences makes it easier for developers to move between React and Vue with confidence.
React Official documentation: https://react.dev/
Vue Official documentation: https://vuejs.org/