icon-dark icon-light
Cover image for Vue Best Practices

Vue Best Practices

1. Use Key With V-For

Vue does the heavy lifting of updating what’s rendered to the screen. But there’s only so much that Vue can know about your app and how it should do these updates.

This is necessary so that Vue can track your component state as well as have a constant reference to your different elements. An example where keys are extremely useful is when using animations or Vue transitions.

You can read a detailed article about “Key attributes in Vue” Here

<!-- Bad -->
<div v-for="product in products"></div>

<!-- Good! -->
<div v-for="product in products" :key="product.id"></div>

2. Use Kebab-Case For Events

It’s always recommended to use kebab-case for event/vue-attributes.

Example of emit:

this.$emit('close-window');

Example in a tag:

<Tab @close-window="handleEvent()" />

3. Props Definition

In committed code, prop definitions should always be as detailed as possible, specifying at least type(s).

Detailed prop definitions have two advantages:

  • They document the API of the component, so that it’s easy to see how the component is meant to be used.
  • In development, Vue will warn you if a component is ever provided incorrectly formatted props, helping you catch potential sources of error.

Prop names should always use camelCase during declaration, but kebab-case in templates/strings and JSX

Bad:

// This is only OK when prototyping
props: ['status'];

Good:

props: {
  status: String;
}

Even Better:

props: {
  status: {
    type: String,
    required: true,
    validator: function (value) {
      return [
        'syncing',
        'synced',
        'version-conflict',
        'error'
      ].indexOf(value) !== -1
    }
  }
}


4. Avoid v-if with v-for

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 the v-if to a container element (e.g. ul, ol).

A smarter solution would be to iterate over a computed property. The above example would look something like this.

<!--Example 1-->
<ul>
  <li v-for="user in activeUsers" :key="user.id">{{ user.name }}</li>
</ul>

<!--Example 2-->
<ul v-if="shouldShowUsers">
  <li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>

5. Single-file Component Filename Casing

Filenames of single-file components should either be always PascalCase or always kebab-case.

PascalCase works best with autocompletion in code editors, as it’s consistent with how we reference components in JS(X) and templates, wherever possible. However, mixed case filenames can sometimes create issues on case-insensitive file systems, which is why kebab-case is also perfectly acceptable.

Bad:

components/
|- mycomponent.vue

components/
|- myComponent.vue

Good:

components/
|- MyComponent.vue

components/
|- my-component.vue

6. Component File Naming conventions

Child components that are tightly coupled with their parent should include the parent component name as a prefix.

Bad:

components/ |- TodoList.vue |- TodoItem.vue |- TodoButton.vue components/ |- SearchSidebar.vue |-
NavigationForSearchSidebar.vue

Good:

components/ |- TodoList.vue |- TodoListItem.vue |- TodoListItemButton.vue components/ |- SearchSidebar.vue |-
SearchSidebarNavigation.vue

7. Stay consistent with your directive shorthand

A common technique among Vue developers is to use shorthand for directives. For example:

  • @ is short for v-on:
  • : is short for v-bind
  • # is short for v-slot

It is great to use these shorthands in your Vue project. But to create some sort of convention across your project, you should either always use them or never use them. This will make your project/codebase more cohesive and readable.


8. Self Closing Components

Components with no content should be self-closing in single-file components, string templates, and JSX - but never in DOM templates.

Components that self-close communicate that they not only have no content, but are meant to have no content. It’s the difference between a blank page in a book and one labeled “This page intentionally left blank.” Your code is also cleaner without the unnecessary closing tag.

Unfortunately, HTML doesn’t allow custom elements to be self-closing - only official “void” elements. That’s why the strategy is only possible when Vue’s template compiler can reach the template before the DOM, then serve the DOM spec-compliant HTML.

Bad:

<!-- In single-file components, string templates, and JSX -->
<MyComponent></MyComponent>

<!-- In DOM templates -->
<my-component />

Good:

<!-- In single-file components, string templates, and JSX -->
<MyComponent />

<!-- In DOM templates -->
<my-component></my-component>

<!-- Or Everywhere -->
<my-component></my-component>

Also note that if you’ve already invested heavily in kebab-case, consistency with HTML conventions and being able to use the same casing across all your projects may be more important than the advantages listed above. In those cases, using kebab-case everywhere is also acceptable.


9. Multi-Attribute Elements

Elements with multiple attributes should span multiple lines, with one attribute per line.

In JavaScript, splitting objects with multiple properties over multiple lines is widely considered a good convention, because it’s much easier to read.

Bad:

<img src="https://vuejs.org/images/logo.png" alt="Vue Logo" />

<MyComponent foo="a" bar="b" baz="c" />

Good:

<img src="https://vuejs.org/images/logo.png" alt="Vue Logo" />

<MyComponent foo="a" bar="b" baz="c" />

10. Simple Expressions In Templates

Component templates should only include simple expressions, with more complex expressions refactored into computed properties or methods.

Bad:

{{ fullName.split(' ').map(function (word) { return word[0].toUpperCase() + word.slice(1) }).join(' ') }}

Good:

<!-- In a template -->
{{ normalizedFullName }}
// The complex expression has been moved to a computed property
computed: {
  normalizedFullName: function () {
    return this.fullName.split(' ').map(function (word) {
      return word[0].toUpperCase() + word.slice(1)
    }).join(' ')
  }
}

11. Don’t call a method on created AND watch

A common mistake Vue developers make is they unnecessarily call a method in created and watch. The thought behind this is that we want to run the watch hook as soon as a component is initialized.

Bad:

watch () {
  property() {
    this.handleChange()
  }
},

created: () {
  this.handleChange()
},

methods: {
  handleChange() {
    // stuff happens
  }
},

Good:

watch () {
  property {
    immediate: true
    handler() {
      this.handleChange()
    }
  }
},

methods: {
  handleChange() {
    // stuff happens
  }
},

12. Store: Observable vs Vuex

Everyone is inclined to using a full blown store with vuex. Though it’s a good approach, I would like to provide an alternative thought process.

If it’s a small project would recommend using Vue observable, which pretty much works the same as store. And for larger projects, using Vuex should be an obvious choice. Now, how do you decide which is a small project or a large project. The approach that I follow is, if I have less than 4 State objects or less than 2 actions/methods, I use Vue Observable (If it’s really required). If it goes beyond that, would recommend using Vuex for sure!

These set of Vue tips hopefully will make your codebase more maintainable, readable, and more professional.

Hope that you found this article on Vue best-practices helpful. ❤️

Loading...