What I wish I knew about Vue when I started

Note: This post was written with Vue 2 in mind.

I've enjoyed working with Vue for the past year, but there are a few key ideas that I wish someone would have instilled in me when I was first starting. This grab bag of advice has served me well and I hope you'll keep these tips in mind for your next project.

1. Keep the template clean

One of the first things I fell in love with about Vue was the separation of templates and scripts. Instead of mixing markup and logic like in JSX, Vue makes it possible to keep templates simple and declarative. The Vue documentation warns against adding too much logic in the template, but there is nothing is stopping you from doing so. Let's look at why you should heed this advice.

Here is some code that capitalizes a string

<div id="example">
  {{ message.toUpperCase() }}
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'hello world'
  },
})

Result:

HELLO WORLD

This isn't bad. It's easy to understand what's happening and the template isn't bloated. The problem appears when the logic is any more complex than a single string method.

What if we wanted to capitalize only the first letter of each word?

<div id="example">
  {{ message.split(' ').map(word =>`${word.substring(0,1).toUpperCase()}${word.substring(1)}`).join(' ') }}
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'hello world'
  },
})

Result:

Hello World

Playing such code golf in the template makes it unclear what's immediately happening

Good news: we can solve both of these problems by using a computed value.

<div id="example">
  {{ titleCaseMessage }}
</div>
var vm = new Vue({
  el: '#example',
  data: {
   message: 'hello world'
  },
  computed: {
    titleCaseMessage () {
      return this.message.split(' ').map(word => `${word.substring(0, 1).toUpperCase()}${word.substring(1)}`).join(' ')
    }
  }
})

Result:

Hello World

Moving the logic to a computed property lets us name it in a way that makes its purpose self-evident. No more wasting time trying to grok what's happening in the template.

2. Computed properties are going to be your best friend

One of the first things I got tripped up on while learning Vue was when to use a computed property instead of a method. Both seemed to accomplish the same thing at a surface level.

The biggest reason to use a computed property is reactivity.

This Stack Overflow answer does a great job explaining the difference, so I'm going to steal borrow their explanation.

Think of computed properties like derived values that will automatically update when one of the underlying values used to calculate it changes. You don't call a computed and it doesn't accept any parameters. You reference a computed property just like you would a data property.

The easiest way to explain this is by jumping into another example. In this case, we only want to render even numbers.

<div id="example">
  <p
    v-for="number in evenNumbers"
    :key="number"
  >
    {{ number }}
  </p>
</div>
var vm = new Vue({
  el: '#example',
  data () {
    return {
      numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    }
  },
  computed: {
    evenNumbers () {
      return this.numbers.filter(number => number % 2 === 0)
    }
  }
})

Result:

2 4 6 8 10

If this.numbers were updated at any time during or after the created lifecycle hook, evenNumbers would automatically be evaluated. This evaluation results in our template re-rendering to include all the new even numbers.

When should I use a method?

You'll find times where you don't want code to be automatically evaluated or you need to pass a parameter— both of those are great reasons to use a method. My most common use for methods is when reactivity isn't a concern, such as making API calls.

3. Reactivity has a few gotchas

I'd be lying if I said I hadn't spent hours trying to debug reactivity issues that were the result of forgetting basic rules. The majority of my headaches came from data not updating.

To demonstrate a mistake I made, let's look at this car object.

<div id="example">
  {{ car }}
</div>
var vm = new Vue({
  el: '#example',
  data: {
   car: {
     paint: 'blue',
     numberOfWheels: 4
   }
  }
})

Result:

{ "paint": "blue", "numberOfWheels": 4 }

Maybe you went on a TV show famous for putting televisions in the trunks of cars

var vm = new Vue({
  el: '#example',
  data: {
   car: {
     paint: 'blue',
     numberOfWheels: 4
   }
  },
  mounted () {
    this.getTrickedOut()
  },
  methods: {
    getTrickedOut () {
      this.car.numberOfTVs = 1
    }
  }
})

Want to guess what {{ car }}will render after the mounted lifecycle?

Result:

{ "paint": "blue", "numberOfWheels": 4 }

But what about numberOfTVs?

Vue can't detect if an object has a property added or removed — you need to instantiate all properties that should be reactive in data. For our example, that means we must explicitly define numberofTVs as a property of car inside our data.

<div id="example">
  {{ car }}
</div>
var vm = new Vue({
  el: '#example',
  data: {
   car: {
     paint: 'blue',
     numberOfWheels: 4,
     numberOfTVs: 0
   }
  },
  mounted () {
    this.getTrickedOut()
  },
  methods: {
    getTrickedOut () {
      this.car.numberOfTVs = 1
    }
  }
})

Result:

{ "paint": "blue", "numberOfWheels": 4, "numberOfTVs": 1 }