Why I use Vue
I use Vue as my main framework since 2019. While Vue is one of the “big three” by adoption just alongside React and Angular people are still usually surprised when I tell it to them. In this post I will explain the reasons behind my choice.
To put it short, I like Vue because it has an emphasis on DX, doesn’t overcomplicate stuff and has everything you need included (and yes, it is still very fast)!
Let’s see some examples:
You write js in <script>, css in <style> and html in <template>
Vue components are written as Single File Components. You write CSS in <style>, html with template engine in <template> and javascript in <script>. It feels like writing a simple html file — a cornerstone of the web. Almost anyone in your team will understand how to make edits in such files.
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">Count: {{ count }}</button>
</template>
<style scoped>
button { color: blue; }
</style>
Non JSX templates
Templates are written as html with attributes like v-for, v-if, v-else. You don’t mess JS with HTML, and in result get more readable code than JSX (ask non-js people!).
<template>
<div v-if="user">
<h1>Hello, {{ user.name }}!</h1>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.title }}
</li>
</ul>
</div>
<p v-else>Please log in</p>
</template>
You write CSS as CSS
You can write regular CSS and just add scoped attribute to <style> to encapsulate it. No need for CSS modules, CSS-in-JS and other stuff. Just write CSS and everything works.
<style scoped>
.card {
padding: 1rem;
border-radius: 8px;
}
/* Won't affect other components */
</style>
Reactivity tools are reasonably named and framework has performance optimizations built-in
So you mark values as reactive with ref, compute values with computed and watch values with watch. Usually you don’t need to think about optimizing these.
// `ref` is the way to mark value as reactive, like useState
const count = ref(0)
// `computed` is value that was computed. It won't be recomputed if all dependent values are the same. Same as useMemo in react
const doubled = computed(() => count.value * 2)
// `watch` watches value, just like useEffect in react
watch(count, (newValue) => {
console.log(`Count changed to ${newValue}`)
})
Components communicate via events
Components communicate with each other via events that go up. This results in much cleaner code without a need of callback prop drilling / overreliance on stores or resolving shadowing issues.
<!-- Child.vue -->
<button @click="$emit('update', newValue)">Update</button>
<!-- Parent.vue -->
<Child @update="handleUpdate" />
Vue ecosystem is great
For most cases you can find exactly one great solution made by someone close to Vue or Nuxt teams. Few examples:
- You may know
viteandvitestthat started as part of the Vue ecosystem, and now they are integral (and the least annoying) parts of the frontend toolchain. - In Vue’s default state manager pinia you can update state like
store.count++. In redux you would need to have at least a PhD in computer science to do that.
Attributes passed to component
Anything that is not a declared prop is passed to component as an attribute. Same works for classes which will be joined in the expected way. Why this is not default behaviour in other frameworks is a mystery for me.
<!-- MyButton.vue - only declares 'label' prop -->
<template>
<button class="btn">{{ label }}</button>
</template>
<!-- Using the component -->
<MyButton label="Click" class="primary large" data-test="btn" />
<!-- Renders: <button class="btn primary large" data-test="btn">Click</button> -->
<!-- Classes are automatically joined! -->
And what I dislike…
I also have a lot of experience with other frameworks (mostly React and Svelte), so I can highlight some downsides.
Occasional quirks with lifecycle and reactivity
In comparison with modern React where component lifecycle is just a function lifecycle, Vue lifecycle and reactivity are more complicated. This usually doesn’t cause any problems, but sometimes I don’t clearly understand how it executes stuff in the correct order.
Tests require special tooling
You must rely on vue-test-utils to write unit tests, which has a learning curve.
Nuxt
You can easily build SPA’s of any size or complexity with just Vue, but for SSR you will need to rely on a framework.
Unlike React which has plenty of SSR frameworks, or Svelte which has first-party SSR framework, Vue has only Nuxt (and few obscure DIY solutions).
Nuxt is a third-party framework, which had its own business model but recently it was acquired by Vercel.
Nuxt is the most widespread and straightforward way to do SSR in Vue. But apart from SSR, the framework comes with all kinds of very opinionated magic. Unlike magic in Vue, this magic in Nuxt breaks every time you walk away from examples.
Another problem is Nuxt’s overreliance on community-maintained modules. These modules usually quickly become unmaintained, while being promoted by all Nuxt resources as the official solution. With almost everything you will find yourself in a loop that looks like this:
— I want to integrate X library into Nuxt. How can I do it?
— Just run
nuxi module add everything-is-done-for-you{trying module}
{Module doesn’t work. Installation guide is wrong. Repo has three commits and the last one was made two years ago by dependabot}
That said, making SSR apps is quite a frustrating process.
I think Vue should have a first-party, non-commercial framework to do SSR, like Svelte does.
To sum up
I think even with its high adoption Vue is still a very undervalued framework.
- It has great DX and it’s accessible for programmers of all backgrounds
- It has great performance, without a need to constantly optimize it
- It’s widely adopted so even quite a big company can find enough people who have hands-on experience with Vue
So, don’t be afraid to try Vue!