A mostly reasonable approach to VueJS
Other Style Guides
- Component based development
- Component structure
- Component naming
- Component data
- v-for / v-if
- Simple expressions in templates
- Quoted attribute values
- Directive shorthands
- Implicit parent-child communication
- Non-flux state manangement
- Testing
- ESLint
- Resources
Always construct your app out of small components which do one thing and do it well.
A component is a small self-contained part of an application. The Vue.js library is specifically designed to help you create view-logic components.
Small components are easier to learn, understand, maintain, reuse and debug. Both by you and other developers.
Each Vue component must be FIRST: Focused (single responsibility), Independent, Reusable, Small and Testable.
If your component does too much or gets too big, split it up into smaller components which each do just one thing. As a rule of thumb, try to keep each component file less than 100 lines of code. Also ensure your Vue component works in isolation. For instance by adding a stand-alone demo.
Using single file components ( SFC ) is prefered, as they promote modularity and the ease of development when working with components.
Single-file components should always have the <template>, <script>, and <style> order, with <style> last, because at least one of the other two is always necessary.
//bad
<style>/* ... */</style>
<script>/* ... */</script>
<template>...</template>
//good
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>Filenames of single-file components should either always be PascalCase.
PascalCase works best with autocompletion in code editors, as it’s consistent with how we reference components in JS(X) and templates, wherever possible.
//bad
components/
|- mycomponent.vue
components/
|- myComponent.vue
//good
components/
|- MyComponent.vueeslint:
vue/name-property-casing
Component names should be multi-word, except for root App components.
This prevents conflicts with existing and future HTML elements, since all HTML elements are a single word.
Each component name must be:
- Meaningful: not over specific, not overly abstract.
- Short: 2 or 3 words.
- Pronounceable: we want to be able to talk about them.
//bad
export default {
name: 'Todo',
// ...
}
//good
export default {
name: 'TodoItem',
// ...
}eslint:
vue/no-shared-component-data
When using the data property on a component (i.e. anywhere except on new Vue), the value must be a function that returns an object. More info
//bad
export default {
data: {
foo: 'bar'
}
}
//good
export default {
data () {
return {
foo: 'bar'
}
}
}eslint:
Prop definitions should be as detailed as possible.
In committed code, prop definitions should always be as detailed as possible, specifying at least type(s) and default value.
//bad
props: ['status']
props: {
status: String
}
//good
props: {
status: {
type: String,
'default': ''
}
}
// Even better!
props: {
status: {
type: String,
'default': '',
required: true,
validator: value => {
return [ 'syncing', 'synced', 'error' ].includes(value);
}
}
}While Vue.js supports passing complex JavaScript objects via these attributes, you should try to keep the component props as primitive as possible. Try to only use JavaScript primitives (strings, numbers, booleans) and functions. Avoid complex objects.
Prop name casing
Prop names should always use camelCase during declaration, but kebab-case in templates and JSX.
We’re simply following the conventions of each language. Within JavaScript, camelCase is more natural. Within HTML, kebab-case is.
//bad
props: {
'greeting-text': String
}<WelcomeMessage greetingText="hi"/>//good
props: {
greetingText: String
}<WelcomeMessage greeting-text="hi"/>Complex computed properties should be split into as many simpler properties as possible.
//bad
computed: {
price: function () {
var basePrice = this.manufactureCost / (1 - this.profitMargin)
return (
basePrice -
basePrice * (this.discountPercent || 0)
)
}
}
//good
computed: {
basePrice: function () {
return this.manufactureCost / (1 - this.profitMargin)
},
discount: function () {
return this.basePrice * (this.discountPercent || 0)
},
finalPrice: function () {
return this.basePrice - this.discount
}
}Computed properties should be synchronous. Asynchronous actions inside them may not work as expected and can lead to an unexpected behaviour, that's why you should avoid them. If you need async computed properties you might want to consider using additional plugin [vue-async-computed]
//bad
computed: {
foo1: async function () {
return await someFunc()
},
}
//good
computed: {
foo1 () {
var bar = 0
try {
bar = bar / this.a
} catch (e) {
return 0
} finally {
return bar
}
},
}It is considered a very bad practice to introduce side effects inside computed properties. It makes the code not predictable and hard to understand.
//bad
computed: {
fullName () {
this.firstName = 'lorem' // <- side effect
return `${this.firstName} ${this.lastName}`
},
reversedArray () {
return this.array.reverse() // <- side effect - orginal array is being mutated
}
}
//good
computed: {
fullName () {
return `${this.firstName} ${this.lastName}`
},
reversedArray () {
return this.array.slice(0).reverse() // .slice makes a copy of the array, instead of mutating the orginal
}
}eslint:
vue/return-in-computed-property
//bad
computed: {
baz () {
if (this.baf) {
return this.baf
}
}
}
//good
computed: {
baz () {
if (this.baf) {
return this.baf
}
return false;
}
}key with v-for is always required on components, in order to maintain internal component state down the subtree. Even for elements though, it’s a good practice to maintain predictable behavior, such as object constancy in animations.
//bad
<ul>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ul>
//good
<ul>
<li v-for="todo in todos" :key="todo.id" >
{{ todo.text }}
</li>
</ul>Never use v-if on the same element as v-for.
There are two common cases where this can be tempting:
-
To filter items in a list (e.g.
v-for="user in users" v-if="user.isActive"). In these cases, replace users with a new computed property that returns your filtered list (e.g.activeUsers). -
To avoid rendering a list if it should be hidden (e.g.
v-for="user in users" v-if="shouldShowUsers"). In these cases, move thev-ifto a container element (e.g.ul,ol).
//bad
<ul>
<li v-for="user in users" v-if="user.isActive" :key="user.id" >
{{ user.name }}
</li>
</ul>
-- or --
<ul>
<li v-for="user in users" v-if="shouldShowUsers" :key="user.id" >
{{ user.name }}
</li>
</ul>
//good
<ul>
<li v-for="user in activeUsers" :key="user.id" >
{{ user.name }}
</li>
</ul>
-- or --
<ul v-if="shouldShowUsers">
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>Component templates should only include simple expressions, with more complex expressions refactored into computed properties or methods.
Complex expressions in your templates make them less declarative. We should strive to describe what should appear, not how we’re computing that value. Computed properties and methods also allow the code to be reused.
//bad
<div>
{{
fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}}
</div>
//good
<div>{{ normalizedFullName }}</div>// The complex expression has been moved to a computed property
computed: {
normalizedFullName() {
return this.fullName.split(' ').map(word => {
return word[0].toUpperCase() + word.slice(1)
}).join(' ');
}
}eslint:
vue/html-quotes
Non-empty HTML attribute values should always be inside quotes (single or double, whichever is not used in JS).
While attribute values without any spaces are not required to have quotes in HTML, this practice often leads to avoiding spaces, making attribute values less readable.
//bad
<input type=text>
<AppSidebar :style={width:sidebarWidth+'px'}>
//good
<input type="text">
<AppSidebar :style="{ width: sidebarWidth + 'px' }">Directive shorthands (: for v-bind: and @ for v-on:) should always be used.
//bad
<input
v-bind:value="newTodoText"
v-on:focus="newTodoFocus"
>
//good
<input
:value="newTodoText"
@focus="newTodoFocus"
>Props and events should be preferred for parent-child component communication, instead of this.$parent or mutating props.
An ideal Vue application is props down, events up. Sticking to this convention makes your components much easier to understand. However, there are edge cases where prop mutation or this.$parent can simplify two components that are already deeply coupled.
The problem is, there are also many simple cases where these patterns may offer convenience. Beware: do not be seduced into trading simplicity (being able to understand the flow of your state) for short-term convenience (writing less code).
Vuex should be preferred for global state management, instead of this.$root or a global event bus.
Managing state on this.$root and/or using a global event bus can be convenient for very simple cases, but are not appropriate for most applications. Vuex offers not only a central place to manage state, but also tools for organizing, tracking, and debugging state changes.
- Whichever testing framework you use, you should be writing tests!
- Strive to write many small pure functions, and minimize where mutations occur.
- Be cautious about stubs and mocks - they can make your tests more brittle.
- We primarily use jest at Monosolutions.
- 100% test coverage is a good goal to strive for, even if it’s not always practical to reach it.
- Whenever you fix a bug, write a regression test. A bug fixed without a regression test is almost certainly going to break again in the future.
Vue Test Utils is the official unit testing utility library for Vue.js. It provides many helper methods to easily get started with testing with Vue.
Linting for single file components in vue can be done using ESLint and eslint-plugin-vue, which is the official eslint plugin for Vue.js
- Example
.eslintrc - Available rules: Vue ESLint Rules
