I’ve been using React for a long time, so when some says “Hey, you should try a different frontend framework”, my answer is always pffft, but I’m already so efficient =P. There have been enough cool developments in Vue, though, to make me want to try it. And man was I pleasantly surprised! Vue has a bunch of neat features that make me jealous as React dev. Let's delve into some Vue patterns that stand out against React's approach!
1. Installation is Fun
The Vue’s out-of-the-box impressions is light-years ahead — npm create vue@latest
feels like a designed experience, while npm create-react-app
is just a plain ol’ NPM command. I prefer interactively selecting which packages to install, as done in Vue, over CRA’s process.
On top of that, the packages installed by Vue provide a more complete dev experience than CRA, covering areas like routing, state management, and code formatting.
Vue’s installation process is awesomely interactive.
Also, can we talk about how helpful the starter page is when you open localhost
for your new project?
All the Vue examples going forward are using the Composition API from Vue 3, which is most equivalent to functional React.
2. Intuitive Reactivity System
Vue's reactivity system feels more intuitive and less boilerplate-heavy compared to React's state management. Vue handles dependency tracking and updates automatically, making state management straightforward.
Vue Example
<template>
<p>{{ count }}</p>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
const count = ref(0);
onMounted(() => {
setInterval(() => {
count.value++;
}, 1000);
});
</script>
React Equivalent
React requires more boilerplate to achieve similar reactivity, especially for effects and state updates that depend on previous state values. Who else has to look up the subtle differences between useEffect
, useMemo
, and useCallback
every time you want to use them? 🙋🏾♂️
function Counter() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
const interval = setInterval(() => {
setCount(currentCount => currentCount + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return <p>{count}</p>;
}
3. Single-File Components
Single-file components (SFCs) are definitely a love/hate thing, but I think they are great, especially for small components. In my opinion, encapsulating HTML, JavaScript, and CSS in one file promotes better organization and readability.
Vue Single File Component
<script setup lang="ts">
import { defineProps } from 'vue'
defineProps({
message: String
})
</script>
<template>
<div class="message-box">
<p>{{ message }}</p>
</div>
</template>
<style scoped>
.message-box {
padding: 20px;
border: 1px solid #eee;
}
</style>
React Equivalent
In React, achieving the same encapsulation typically involves separating concerns into different files or adopting CSS-in-JS libraries, which can add complexity.
function MessageBox({ message }) {
return (
<div className="message-box">
<p>{message}</p>
</div>
);
}
// CSS is managed separately or through CSS-in-JS solutions
4. Conditional Rendering
Vue's directive system (v-if
, v-for
, etc.) provides a more readable approach to conditional rendering than React. This advantage becomes more pronounced as your if/else statements become more complicated.
Vue Example with Directives
<script setup lang="ts">
import { ref } from 'vue';
const isSubscribed = ref(false);
const isSubscriptionExpiring = ref(true);
// Above properties updated dynamically...
</script>
<template>
<div>
<p v-if="!isSubscribed">Please subscribe to access premium features.</p>
<p v-else-if="isSubscriptionExpiring">Your subscription is expiring soon. Please renew to continue enjoying premium features.</p>
<p v-else>Welcome back, valued premium user!</p>
</div>
</template>
React Equivalent
function SubscriptionStatus() {
const isSubscribed = false;
const isSubscriptionExpiring = true;
// Above properties updated dynamically...
return (
<div>
{isSubscribed ? (
isSubscriptionExpiring ? (
<p>Your subscription is expiring soon. Please renew to continue enjoying premium features.</p>
) : (
<p>Welcome back, valued premium user!</p>
)
) : (
<p>Please subscribe to access premium features.</p>
)}
</div>
);
}
You can of course move the JSX-based logic into a JS function, but the initial pass is easier to understand in Vue.
5. Computed Properties
Vue's computed properties simplify the handling of complex logic that depends on reactive data. They are automatically cached and only re-evaluated when their dependencies change, optimizing performance.
Vue Example
<script setup lang="ts">
import { computed, ref } from 'vue'
const price = ref(10)
const quantity = ref(3)
const totalPrice = computed(() => price.value * quantity.value)
</script>
<template>
<p>Total price: {{ totalPrice }}</p>
</template>
React Equivalent
In React, similar functionality requires useMemo hook, adding complexity and verbosity to component logic.
function TotalPrice() {
const [price, setPrice] = React.useState(10);
const [quantity, setQuantity] = React.useState(3);
const totalPrice = React.useMemo(() => {
return price * quantity;
}, [price, quantity]);
return <p>Total price: {totalPrice}</p>;
}
6. Streamlined State Management
Pinia is one of the recommended state management solutions for Vue. Thanks to its tight integration with Vue and thoughtful API, it feels more streamlined than the equivalent code in React + Redux.
Vue with Pinia
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
actions: {
increment() {
this.count++
},
},
})
// components/MyCounter.vue
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
</script>
<template>
<button @click="counter.increment">Increment</button>
<div>Current Count: {{ counter.count }}</div>
</template>
React with Redux
// counterSlice.js
import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1
}
},
})
export const { increment } = counterSlice.actions
export default counterSlice.reducer
// Counter.js
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'
export function Counter() {
const count = useSelector((state) => state.counter.value)
const dispatch = useDispatch()
return (
<button onClick={() => dispatch(increment())}>
Increment
</button>
<span>{count}</span>
)
}
In Conclusion
My initial foray into learning Vue has expanded my perspectives on good design patterns and well-built APIs. It’s clear that Vue prioritized developer experience and application performance from the first commit. I think the biggest lesson here is that regardless of your skill level or what tools you have mastered, you can always learn from trying something new!
Is there anything else you like about Vue? Let me know in the comments!
PS. I share content like this post 2x / week. If you’re interested in frontend, backend, and how to become a better full stack developer,subscribe to my newsletter!