There’s some simple patterns I tend to follow in my Vue projects that I figure work well enough to share, so here ya go.
I’m really curious to see what other people do for these cases, so please share.
Loading state
I use a simple loading
object of true/false flags that I toggle whenever that loading state changes. Messaging, loading indicators, and entire content sections are driven by these flags.
Even though there are cases where I could use the presence or absence of the relevant data, using a separate object provides the flexibility needed for any design requirement.
An example:
<template>
<div>
<div v-if="loading.users">Loading users</div>
<table v-if="!loading.users">
...
</table>
</div>
</template>
<script>
export default {
data() {
return {
users: [],
loading: {users: false}
}
},
created() {
this.loading.users = true;
fetch('/users')
.then(users => this.users = users)
.catch(console.error)
.finally(() => this.loading.users = false)
}
}
</script>
Error messaging
Similar to loading states, I set up a simple errors
object and toggle flags. I’ve found error messages are best done in the template rather than in the errors object because one error can sometimes trigger multiple UI bits.
An example:
<template>
<div>
<div v-if="errors.fetchUsers">Failed to load users.</div>
</div>
</template>
<script>
export default {
data() {
return {
users: [],
errors: {fetchUsers: false}
}
},
created() {
fetch('/users')
.then(users => this.users = users)
.catch(err => {
this.errors.fetchUsers = true;
console.error(err);
})
}
}
</script>
Occasionally a component needs to know if there are any errors at all. That's really easy to check for:
// Basic programmatic check
const hasErrors = Object.values(this.errors).some(err => err)
// Or as a computed
computed: {
hasErrors: function () {
return Object.values(this.errors).some(err => err)
}
}
Avoid Event Modifiers
From the docs:
<form v-on:submit.prevent="onSubmit"></form>
That .prevent
is a shortcut to the already short e.preventDefault()
. The cost of proprietary markup like this scattered all over your app trumps their negligible convenience.
A strength Vue (and Riot) have is their plainness. That makes them easier to adopt and easier to replace. Using too much of a framework's special sauce increases the dependency - not good! And makes it harder for newcomers to understand your code as well.
Flatter component hierarchy
I avoid nesting Vue components beyond the third layer. The fourth layer is implemented as Custom Elements because I strongly prefer to write vanilla js any time there isn't a need for framework-specific features.
My Vue (and Riot) projects look like this:
This is an ideal design I could never quite achieve with React because React struggles a bit with Custom Elements even though they are a web standard.
Shared modules
This one may be obvious, but I sometimes see over-engineered solutions for these kinds of problems so I figured it's worth sharing.
Instead of creating a Vue component or custom directive or other Vue-dependent solution, I strive to use simple Vue-free modules where possible. For example, in several components I need to format a raw number of bytes as KB, MB, GB, etc. I export a function that does this and import it in the components that need it:
// data-utils.js
// No Vue allowed!
export function formatBytes(bytes = 0) {
// Zero is a valid value
if (bytes <= 0) return '0 bytes';
// Handle non-zero falsy
if (!bytes) return '';
// Format value, e.g. "5 MB"
const k = 1024;
const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB'];
const size = Math.floor(Math.log(bytes) / Math.log(k));
const num = parseFloat((bytes / Math.pow(k, size)).toFixed(2))
return `${num} ${sizes[size]}`;
}
<template>
<div>
<p>File size is {{ formatBytes(file.size) }}</p>
</div>
</template>
<script>
import {formatBytes} from '../data-utils';
export default {
data() {
return {
file: new File()
}
},
methods: {
formatBytes
}
}
</script>
I usually end up with a dozen or more of these and they're so much easier to write, use, and test when there's no dependency on Vue itself even though they're for a Vue project. Sometimes these end up graduating to a shared package that can be used by other projects because they're decoupled from Vue.
Beyond that I'm just using the basic features of Vue as-is and that alone takes me incredibly far. It's this straight-forwardness that makes Vue more powerful than other more ambitious and proprietary frameworks. Vue gives you so much more than it takes in my experience, and in many cases you don't have to let it take either.
Beyond the basics, what patterns are you using in Vue?
Top comments (20)
I‘m not sure I agree with this statement. The purpose of a framework is to make certain development tasks easier. To make use of those you will pretty much always be required to write framework-specific code (if not, why would you be using the framework to begin with?) Of course it‘s debatable whether syntactic sugar is always worth the reduced clarity of intent, but I don‘t see the dependency on the framework to be the issue here.
I‘ve not had to convert a huge codebase from one framework to the other. Usually it‘s been complete rewrites, occassionally taking inspiration from the old code, but never could I just translate Vue into e.g. React just one-to-one without also having to change tiny details that are just too different between these frameworks anyways.
Other people‘s mileage may vary, but I think not making use of reasonable syntactic sugar due to fear of maybe having to translate it into a different framework at some point in the future will make your life harder, not easier. This is not to say that .prevent specifically is a worthwile feature, but there most certainly are other framework-specific features that would fit into this category.
I prefer composition, so when possible I take opportunities - even small ones like avoiding low-value template shortcuts - to flatten the dependency graph. This leaves as many pieces of the system as possible open to change or replacement.
Vue helps with structure and managing the render cycle - the biggest pieces of an app - but I don't want it doing much more than that for me. There's better options, usually vanilla js, for other parts of an app and I don't want to lock myself in or bloat my project by YOLO-ing the framework. Thankfully Vue is not like React or Angular or Ember in that way, so it's not possible to get that deep, but I avoid it just the same.
Could you elaborate more on the meaning of "I avoid nesting Vue components beyond the third layer."
I am not familiar with third or fourth layer, in fact I am not sure what the first and second layer are supposed to be, what do they refer to?
Typically Vue apps start with an
app.vue
component. It usually sets up a high-level layout for the product and routed views. That’s layer 1.The app is then broken down into “views” which are mostly 1:1 with the logical pages of your product. GitHub, for example, would have repos list view, repo details view, user profile view, etc. These pages are layer 2.
Each page has some template code, state and other logic code, and maybe some styles of it’s own and it includes other components. Repo details page from the GitHub example would include components like files list, PRs, issues, settings, etc. These components are layer 3.
When I’m at layer 3 and I see a strong need for another layer of abstraction I go for simple Custom Elements, the M- library to be specific (I’m the creator).
Thanks for explaining. Just to clarify if you have:
app.js
search.vue
list.vue
favoriteButton.vue
You would implement the favorite button component using custom elements?
The favorite button is a button for adding the item on the search results list to your favorite items.
The same favorite button is also used on the detal page.
That would be considered a 4th layer?
Generally yes. However, if I take that example at face value then it seems like there’s no need for a fourth layer at all. The list component and the detail component both use a regular button and their click handler uses whatever API there is for adding an “item” to “favorites”. No compelling reason for an abstraction here and just because two event handlers call the same storage API doesn’t mean the code isn’t DRY either. That’s my simple approach anyway.
Well the button does a few things.
A star on the button changes color depending on if the item is already in the favorite list, so have to keep that state.
If the user is not logged in, a popup is shown, asking the user if he wants to login.
So having it as a component is convenient, cause it's easy to reuse. I am not sure what would be the advantage of using custom elements.
Ah, then yeah I'd go CE.
There's no huge must-have advantage in doing this, I just mostly prefer to use vanilla js when possible. The minor advantage in this case being one more little piece of the app is pure dependency-free js, which means the app is just a teeny weeny bit smaller and faster and this CE could be possibly be used with a non-Vue project should the need arise. The big gains come as these little components (and the plain js modules I mentioned as well) add up over time as the project scales. If there's 20, 30, or more of these vanilla components you're saving real bytes and overhead and a good portion of your codebase is emancipated.
Definitely not ground-breaking stuff, but I'll take these little wins where I can, especially if it means more vanilla code.
Thanks, seems interesting to look more into CE
It is virtually impossible to avoid how deep components are nested the second you use any 3rd party component or library. Popular libraries like Vuetify and Quasar all have componets that are more than 3 levels deep.
I use my own UI library of custom HTML tags and Custom Elements, which has been really good.
Excellent article - thanks for sharing! You inspired me to start using reactive objects for loading state and errors.
I agree fully about putting non-UI utility functions in plain Javascript files. I usually drop such functions into a file named Util.js. Being vanilla Javascript and pure functions, it's easy to bring parts of that file over to new projects as needed.
Yeah it’s so handy when you’ve got a chunk of vanilla js you can just freely use when and where you want to take it
But what makes a framework worth using is its "special sauce". That's what makes building rhings easier or faster or safer. If your goal is to avoid using the tools that the framework provides ... Then why use the framework at all?
True, but didn’t I say “too much special sauce”?
I use Vue for application structure and managing the render cycle when state changes. Those are the two primary benefits Vue offers. Yes, like other frameworks, Vue can do more than that, but I don’t just go all in because I’m using a given framework. I like to keep things flat and vanilla when there isn’t a compelling reason not to. And I like the freedom to use other libraries that are better than what the framework offers (things like dates, i18n, UI library)
My thoughts, for a system of 10+ years:
Hey guys,
Check this open source project that we develop for the community beercss.com 🍺
In terms of the loading and error states, it can sometimes get a bit clunky with all the booleans, and it’s nice to have a lookup for the single state that applies to something - eg, it can be in an error state, a loading state, a resolved state, whatever, but only one at a time. Then in your catch you change something like this.state.users from “loading” to “error” and otherwise from “loading” to “resolved”.
Love the others, especially the custom element idea. Kinda want to look into that. Thanks for sharing.
Ok that’s cool. I think…so you kind of define an enum of states
'error', 'loading', etc.
and assign one to the things that are currently in that state? I like how that could work for stuff that kind of goes through a process of state changesloading —> error —> resolved
, but I don’t think it works for granular errors like input validation of a large form.Thanks for sharing!
loading = null/true/false to catch the first page/component rendering
Question: I'm not familiar at all with the way you calculate the size unit index with the
Math.log
. Can someone explain the method ?