This guide provides a uniform way to structure your Vue.js code. Making it:
This guide is inspired by the RiotJS Style Guide by De Voorhoede .
this
to component
this.$parent
this.$refs
with caution Always construct your app out of small modules which do one thing and do it well.
A module is a small self-contained part of an application. The Vue.js library is specifically designed to help you create view-logic modules .
Small modules are easier to learn, understand, maintain, reuse and debug. Both by you and other developers.
Each Vue component (like any module) 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.
↑ back to Table of Contents
Each component name must be:
Vue component names must also be:
app-
namespaced
: if very generic and otherwise 1 word, so that it can easily be reused in other projects. <!-- recommended --> <app-header></app-header> <user-list></user-list> <range-slider></range-slider> <!-- avoid --> <btn-group></btn-group> <!-- short, but unpronounceable. use `button-group` instead --> <ui-slider></ui-slider> <!-- all components are ui elements, so is meaningless --> <slider></slider> <!-- not custom element spec compliant -->
↑ back to Table of Contents
Vue.js's inline expressions are 100% Javascript. This makes them extremely powerful, but potentially also very complex. Therefore you should keep expressions simple .
If it gets too complex or hard to read move it to methods or computed properties !
<!-- recommended --> <template> <h1> {{ `${year}-${month}` }} </h1> </template> <script type="text/javascript"> export default { computed: { month() { return this.twoDigits((new Date()).getUTCMonth() + 1); }, year() { return (new Date()).getUTCFullYear(); } }, methods: { twoDigits(num) { return ('0' + num).slice(-2); } }, }; </script> <!-- avoid --> <template> <h1> {{ `${(new Date()).getUTCFullYear()}-${('0' + ((new Date()).getUTCMonth()+1)).slice(-2)}` }} </h1> </template>
↑ back to Table of Contents
While Vue.js supports passing complex JavaScript objects via these attributes, you should try to keep the component options as primitive as possible . Try to only use JavaScript primitives (strings, numbers, booleans) and functions. Avoid complex objects.
Use a component attribute per option, with a primitive or function as value:
<!-- recommended --> <range-slider :values="[10, 20]" min="0" max="100" step="5" :on-slide="updateInputs" :on-end="updateResults"> </range-slider> <!-- avoid --> <range-slider :config="complexConfigObject"></range-slider>
↑ back to Table of Contents
In Vue.js your component options are your API. A robust and predictable API makes your components easy to use by other developers.
Component options are passed via custom HTML attributes. The values of these attributes can be Vue.js plain strings ( :attr="value"
or v-bind:attr="value"
) or missing entirely. You should harness your component options
to allow for these different cases.
Harnessing your component options ensures your component will always function (defensive programming). Even when other developers later use your components in ways you haven't thought of yet.
type
option to validate
values to an expected type. [1*]
<template> <input type="range" v-model="value" :max="max" :min="min"> </template> <script type="text/javascript"> export default { props: { max: { type: Number, // [1*] This will validate the 'max' prop to be a Number. default() { return 10; }, }, min: { type: Number, default() { return 0; }, }, value: { type: Number, default() { return 4; }, }, }, }; </script>
↑ back to Table of Contents
this
to component
Within the context of a Vue.js component element, this
is bound to the component instance. Therefore when you need to reference it in a different context, ensure this
is available as component
.
In other words: Do NOT
code things like const self = this;
anymore. You're safe using Vue components.
this
to a variable named component
the variable tells developers it's bound to the component instance wherever it's used. <script type="text/javascript"> export default { methods: { hello() { return 'hello'; }, printHello() { console.log(this.hello()); }, }, }; </script> <!-- avoid --> <script type="text/javascript"> export default { methods: { hello() { return 'hello'; }, printHello() { const self = this; // unnecessary console.log(self.hello()); }, }, }; </script>
↑ back to Table of Contents
Make it easy to reason and follow a sequence of thoughts. See the How.
name
attribute. Using vue devtools
and that attribute will make your development/testing easier; Component structure:
<template lang="html"> <div class="Ranger__Wrapper"> <!-- ... --> </div> </template> <script type="text/javascript"> export default { // Do not forget this little guy name: 'RangeSlider', // compose new components extends: {}, // component properties/variables props: { bar: {}, // Alphabetized foo: {}, fooBar: {}, }, // variables data() {}, computed: {}, // when component uses other components components: {}, // methods watch: {}, methods: {}, // component Lifecycle hooks beforeCreate() {}, mounted() {}, }; </script> <style scoped> .Ranger__Wrapper { /* ... */ } </style>
↑ back to Table of Contents
Vue.js provides all Vue handler functions and expressions are strictly bound to the ViewModel. Each component events should follow a good naming style that will avoid issues during the development. See the Why below.
↑ back to Table of Contents
this.$parent
Vue.js supports nested components which have access to their parent context. Accessing context outside your vue component violates the FIRST
rule ofcomponent based development. Therefore you should
avoid using this.$parent
.
↑ back to Table of Contents
this.$refs
with caution
Vue.js supports components to have access to other components and basic HTML elements context via ref
attribute. That attribute will provide an accessible way through this.$refs
to a component or DOM element context. In most cases, the need to access other components
context via this.$refs
could be avoided. This is why you should be careful when using it to avoid wrong component APIs.
this.$refs
on components should be used when props and events are exhausted and having it makes sense (see the example below). this.$refs
to access DOM elements (instead of doing jQuery
, document.getElement*
, document.queryElement
) is just fine, when the element can't be manipulated with data bindings or for a directive. <!-- good, no need for ref --> <range :max="max" :min="min" @current-value="currentValue" :step="1"></range>
<!-- good example of when to use this.$refs --> <modal ref="basicModal"> <h4>Basic Modal</h4> <button class="primary" @click="$refs.basicModal.close()">Close</button> </modal> <button @click="$refs.basicModal.open()">Open modal</button> <!-- Modal component --> <template> <div v-show="active"> <!-- ... --> </div> </template> <script> export default { // ... data() { return { active: false, }; }, methods: { open() { this.active = true; }, hide() { this.active = false; }, }, // ... }; </script>
<!-- avoid accessing something that could be emitted --> <template> <range :max="max" :min="min" ref="range" :step="1"></range> </template> <script> export default { // ... methods: { getRangeCurrentValue() { return this.$refs.range.currentValue; }, }, // ... }; </script>
↑ back to Table of Contents
Vue.js component elements are custom elements which can very well be used as style scope root. Alternatively the component name can be used as CSS class namespace.
Use the component name as a namespace prefix based on BEM and OOCSS and
use the scoped
attribute on your style class. The use of scoped
will tell your Vue compiler to add a signature on every class that your <style>
have. That signature will force your browser (if it supports) to apply your components CSS on all tags that compose your component, leading to a no leaking css styling.
<style scoped> /* recommended */ .MyExample { } .MyExample li { } .MyExample__item { } /* avoid */ .My-Example { } /* not scoped to component or module name, not BEM compliant */ </style>
↑ back to Table of Contents
A Vue.js component instance is created by using the component element inside your application. The instance is configured through its custom attributes. For the component to be used by other developers, these custom attributes - the component's API - should be documented in a README.md
file.
README.md
is the de facto standard filename for documentation to be read first. Code repository hosting services (Github, Bitbucket, Gitlab etc) display the contents of the the README's, directly when browsing through source directories. This applies to our module directories as well.
Add a README.md
file to the component's module directory:
range-slider/ ├── range-slider.vue ├── range-slider.less └── README.md
Within the README file, describe the functionality and the usage of the module. For a vue component its most useful to describe the custom attributes it supports as those are its API:
The range slider lets the user to set a numeric range by dragging a handle on a slider rail for both the start and end value.
This module uses the noUiSlider for cross browser and touch support.
<range-slider>
supports the following custom component attributes:
attribute | type | description |
---|---|---|
min
|
Number | number where range starts (lower limit). |
max
|
Number | Number where range ends (upper limit). |
values
|
Number[] optional |
Array containing start and end value. E.g. values="[10, 20]"
. Defaults to [opts.min, opts.max]
. |
step
|
Number optional | Number to increment / decrement values by. Defaults to 1. |
on-slide
|
Function optional |
Function called with (values, HANDLE)
while a user drags the start ( HANDLE == 0
) or end ( HANDLE == 1
) handle. E.g. on-slide={ updateInputs }
, with component.updateInputs = (values, HANDLE) => { const value = values[HANDLE]; }
. |
on-end
|
Function optional |
Function called with (values, HANDLE)
when user stops dragging a handle. |
For customising the slider appearance see the Styling section in the noUiSlider docs .
↑ back to Table of Contents
Add a index.html
file with demos of the component with different configurations, showing how the component can be used.
↑ back to Table of Contents
Linters improve code consistency and help trace syntax errors. .vue files can be linted adding the eslint-plugin-html
in your project. If you choose, you can start a project with ESLint enabled by default using vue-cli
;
To allow linters to extract the scripts from your *.vue
files,
put script inside a <script>
component andkeep component expressions simple(as linters don't understand those). Configure your linter to allow global variables vue
and component opts
.
ESLint requires an extra ESLint HTML plugin to extract the script from the component files.
Configure ESLint in modules/.eslintrc
(so IDEs can interpret it as well):
{ "extends": "eslint:recommended", "plugins": ["html"], "env": { "browser": true }, "globals": { "opts": true, "vue": true } }
Run ESLint
eslint modules/**/*.vue
JSHint
can parse HTML (using --extra-ext
) and extract script (using --extract=auto
).
Configure JSHint in modules/.jshintrc
(so IDEs can interpret it as well):
{ "browser": true, "predef": ["opts", "vue"] }
Run JSHint
jshint --config modules/.jshintrc --extra-ext=html --extract=auto modules/
Note: JSHint does not accept vue
as extension, but only html
.
↑ back to Table of Contents
Fork it and Pull Request what you think it should be good to have or just create an Issue .
@miljan-aleksic on issue #1