I work daily with React. In recent days I decided to try Vue for the first time.
I enjoyed the experience, so I decided to share my thoughts.
Frontend Framework
Do you know Create React App
? Class Components
? React Hooks
? Next.js
?
Great, so you know why their Vue equivalent exists.
To scaffold a project with a lot of DX stuff already configured, React has Create React App
, Vue has Vue CLI
. Recently, both Create React App
and Vue CLI
are in maintenance mode. Now is recommended to use Vite
for both, unless you use a meta framework (Next.js and Nuxt).
React has Class Components
and Vue has Options API
. React has React Hooks
and Vue Composition API
. React has Next.js
and Vue Nuxt.js
.
Key differences, React vs Vue
The ecosystem
React is a library, Vue is a framework.
This means that Vue has a wider set of official tools, integrated as a whole "framework". React, on the other end, need to be extended by third-party tools.
In Vue, the core team defines the way, in React, the community drives everything.
I'm not saying that Vue doesn't have an active community, but that Vue team guides a lot more than React one.
What do I mean by "ecosystem"?
Think about these common "parts" of a frontend app:
Client Side Routing, which handles the navigation between different pages of your app, even if the browser never redirects actually (the core of a SPA, Single Page Application)
Global State Management, which handles the share of state between multiple components without passing props down to every level of the app tree.
How do you solve them?
The Vue team authored Vuex and Pinia for Global State Management, and Vue Router for Client Side Routing.
React has no official tools, but this means that a lot of third-party good libraries have emerged over time to fill the gap. Like Redux for Global State Management and React Router or Reach Router for Client Side Routing.
Think about this as ...
React gives the world an essential building block for handling reactivity in a Frontend App and, but has no official way to structure the rest of the app, leaving this duty to the community.
Vue instead gives the reactivity system and also a toolbelt with a solid opinion on how to handle everything, so there is an official package for every common problem.
No best or worst.
Both are great declarative libraries, with a good DX.
Don't be a blind fanboy :)
The render runs only once
In react, a render function or a function component re-runs after every state changes.
React compare the result with the current DOM state and decide if to "reconcile" or not.
Instead, in Vue, the template (the render function equivalent) is run only once.
I don't know much about how internal works in Vue, but if you put a console.log inside the script portion of a template it will run only once.
No need for dependencies array
Vue reactivity system is different from React one.
In React you must optimize your code using useMemo
, useCallback
and other things to avoid unnecessary computation and bugs.
I'm referring mainly to the dependencies array.
In Vue all these duties are in charge of the library.
Vue takes care of analyzing and understanding when a piece of code, that depends on some reactive values needs to be recalculated.
No need to define dependencies.
Beginners could find this a winning point in favor of Vue.
A brief history of Vue
History
Vue.js was first released in February 2014 by ex-Google engineer Evan You.
It is an open-source progressive JavaScript framework used for building user interfaces (UIs) and single-page applications.
The Options API was the original API for Vue.js.
The Composition API was introduced later in Vue 3.0, which was released in September 2020.
Evolution: Options API and Composition API
Let's be honest, 99,9% of React nowadays is written in hook style. But imagine that in your work you inherit a legacy project written in Class Components... better you don't feel lost.
So a React beginner should spend a little bit of time on Class Components even if the standard way of writing React is with Hooks.
The same with Vue.
Don't you agree? Let me know it.
Vue initial API is now called "Options API" and is considered legacy.
Vue 3 introduced a new style of writing Vue, that is now the standard,and it is known as "Composition API".
React has had the same evolution, with React Hooks as successor of Class Components.
Compare Vue and React
Take this React class component
import React from 'react';
class Counter extends React.Component {
constructor(props) {
super(props);
// decalare local state
this.state = {
count: 0
};
// bind "this" to event handlers
this.handleIncrementClick = this.handleIncrementClick.bind(this);
this.handleDecrementClick = this.handleDecrementClick.bind(this);
}
// define event handlers
handleIncrementClick() {
this.setState({ count: this.state.count + 1 });
}
handleDecrementClick() {
this.setState({ count: this.state.count - 1 });
}
// render the template
render() {
return (
<div>
<h1>Counter: {this.state.count}</h1>
<button onClick={this.handleIncrementClick}>+</button>
<button onClick={this.handleDecrementClick}>-</button>
</div>
);
}
}
In this class component, we define a state
object that includes a count
property, which will hold the current value of the counter. We also define two methods, handleIncrementClick
and handleDecrementClick
, which increment and decrement the count property of the state object, respectively.
In the render method, we display the current value of count in an h1 element, and we include two buttons that call the handleIncrementClick
and handleDecrementClick
methods, respectively, when clicked. When either button is clicked, the count property of the state object is updated, which triggers a re-render of the component and updates the displayed value of the counter.
This is the Vue counterpart in Options API
<template>
<div>
<h1>Counter: {{ count }}</h1>
<button @click="incrementCount">+</button>
<button @click="decrementCount">-</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
incrementCount() {
this.count++;
},
decrementCount() {
this.count--;
}
}
}
</script>
The data
function must return an object that is the local state
.
Under methods
, you defined incrementCount
and decrementCount
. Vue under the hood takes care of "binding" the this
instance to "methods" handlers, so we don't have to. In addition, updating the local state is done in a mutable fashion, instead of React's immutable.
Vue uses Proxy under the hood, so, in reality, you are not mutating the "real" state object but the proxied one. But from a developer's point of vue view it's less code.
But the overall component code is almost identical.
Ok, this was Class Components vs Options API.
Now let's take a look to React Hook vs Composition API.
React
const Counter = () => {
const [count, setCount] = React.useState(0);
const incrementCount = () => setCount(prev => prev+1);
const decrementCount = () => setCount(prev => prev-1);
return (
<div>
<h1>Counter: {count}</h1>
<button onClick={incrementCount}>+</button>
<button onClick={decrementCount}>-</button>
</div>
)
}
Vue Composition API
<script setup>
import {ref} from 'vue';
const count = ref(0);
const incrementCount = () => count.value++;
const decrementCount = () => count.value--;
</script>
<template>
<div>
<h1>Counter: {{count}}</h1>
<button @click="incrementCount">+</button>
<button @click="decrementCount">-</button>
</div>
</template>
They look similar.
For a good description of the entire reactivity system of Vue read Template Syntax and Reactivity Fundamentals. This is not a tutorial, so take a look there if something is not clear...
The Vue way
It's time to explore some Vue distinctive stuff, that makes it different from React.
v-model
Take this React code that renders an input
const InputComponent = () => {
const [inputValue, setInputValue] = useState('');
const handleChange = (e) => setInputValue(e.target.value)
return (
<div>
<input
type="text"
value={inputValue}
onChange={handleChange}
/>
<p>You entered: {inputValue}</p>
</div>
)
}
... and look it in Vue with Composition API
<script setup>
import {ref} from 'vue';
const inputValue = ref('');
</script>
<template>
<div>
<input type="text" v-model="inputValue" />
<p>You entered: {{ inputValue }}</p>
</div>
</template>
... and Options API
<template>
<div>
<input type="text" v-model="inputValue" />
<p>You entered: {{ inputValue }}</p>
</div>
</template>
<script>
export default {
data() {
return {
inputValue: ''
}
},
}
</script>
A lot less code.
Great, don't you think???
v-model
does all the boring stuff of the Two-Way Data Binding between template and component instance.
This is a great DX improvement of Vue, when compared to React.
Can I use the React way? Yes, in Vue you can use the React "way" if you want.
<script setup>
const inputValue = ref('');
</script>
<input
:value="inputValue"
@input="(event) => inputValue.value = event.target.value"
/>
computed
Computed data, is data that is derived from the state, and should be automatically recalculated on every state update.
Let's augment the Counter example with a doubledCount
derived value.
In React
const Counter = () => {
const [count, setCount] = React.useState(0);
const doubledCount = count * 2;
// here a useMemo version if computation is expensive
// const doubledCount = useMemo(() => count * 2, [count]);
const incrementCount = () => setCount(prev => prev+1);
const decrementCount = () => setCount(prev => prev-1);
return (
<div>
<h1>Count: {count}</h1>
<h1>Doubled Count: {doubledCount}</h1>
<button onClick={incrementCount}>Increment</button>
<button onClick={decrementCount}>Decrement</button>
</div>
);
}
...in Vue Composition API
<script setup>
import {ref, computed} from 'vue';
const count = ref(0);
const doubledCount = computed(() => count.value * 2);
</script>
<template>
<div>
<h1>Count: {{ count }}</h1>
<h1>Doubled Count: {{ doubledCount }}</h1>
<button @click="incrementCount">Increment</button>
<button @click="decrementCount">Decrement</button>
</div>
</template>
... and Options API
<template>
<div>
<h1>Count: {{ count }}</h1>
<h1>Doubled Count: {{ doubledCount }}</h1>
<button @click="incrementCount">Increment</button>
<button @click="decrementCount">Decrement</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
computed: {
doubledCount() {
return this.count * 2;
}
},
methods: {
incrementCount() {
this.count++;
},
decrementCount() {
this.count--;
}
}
}
</script>
You can think of "computed" as useMemo without dependency.
Not so much difference between React and Vue here.
Emitting events
Think about a parent component, that renders a child component and wants to be notified when the child is clicked.
In React you "pass" down a function via props.
import React from 'react';
const ChildComponent = ({ onClick }) => (
<button onClick={onClick}>Click me!</button>
);
const ParentComponent = () => {
const handleChildClick = () => {
console.log('Child is clicked!!!!!');
}
return (
<div>
<h1>Hello, world!</h1>
<ChildComponent onClick={handleChildClick} />
</div>
);
}
in Vue instead you "emit" an event (like in native DOM elements) from the child component and "listen" to that event in the parent component.
This is Vue Composition API
<script setup>
import { defineEmits } from 'vue';
// define all event that this component can emit...
const emits = defineEmits(['clicked']);
const handleClick = () => {
emits('clicked');
}
</script>
<template>
<button @click="handleClick">Click me!</button>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
const handleChildClick = () => {
console.log('Child is clicked!!!!!');
}
</script>
<template>
<div>
<h1>Hello, world!</h1>
<ChildComponent @clicked="handleChildClicked" />
</div>
</template>
... and Options API
<template>
<div>
<h1>Hello, world!</h1>
<ChildComponent @clicked="handleChildClicked" />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
name: "ParentComponent",
//
// this registration of "components" is required
// before using a child component with Options API.
components: {
ChildComponent
},
methods: {
handleChildClicked() {
console.log('Hello');
}
}
}
</script>
<template>
<button @click="handleClick">Click me!</button>
</template>
<script>
export default {
name: "ChildComponent",
methods: {
handleClick() {
this.$emit('clicked');
}
}
}
</script>
The idea of emitting an event has the advantage that from the ChildComponent code there is no need to check if his parent has provided a function or not (like you would do in react when the "onClick" prop is optional).
The child emits the event anyway, and if someone is listening to it will be notified.
Conclusion
I started learning Vue with Options API, founding it a bit verbose compared to React Hooks.
But when tried Composition API I felt at home.
The code is readable and without a "class-like" boilerplate, and the overall experience is great.
Vue has some "magic", that makes code easier to read.
The only downside is that it feels weird to write js code inside quotes like it was a string as happens in HTML attributes.
And you?
What do you think about Vue?
Do you prefer it over React?
Why?
Top comments (6)
The best thing about Vue, by far, is the reactivity system. No need for dependency arrays, and having to deep-compare props on every render. Honestly it’s kinda hard to go back to useCallback!
+1 to that. Now with defineModel() it will become even easier to make reactive components where you would usually need to use so proxy and computed value to update.
I personally started with Vue and it was quite difficult to get into React with the FCs. It just seemed much more complicated. 😬
I really get your point with writing code into the quotes 😅
What I really love about Vue is using the Volar extension for VSCode. I has many useful features as for example inline type hinting for props you define for a component.
Yes, I started on with Vue+Javascript in Stackblitz and CodeSandbox.
Then i moved to Typescript, and found some bugs in the language server of these two sandbox platform (Codesandbox by far better and Stackblitz faster) and so moved to local VS Code with both Volar extensions:
It's incredible how much your code is typed correctly.
Great work from authors.
Js inside string is weird !! also the fact that @click="handleClick" and @click="handleClick()" is the same.
Wondering how can I curry a function on the fly in Vue ?!?!
If you want it to look even More like react you could try jsx syntax.
Thanks for talking about this.
I will try for sure.