Clean up your Vue modules with ES6 Arrow Functions

By JacobBennett

Recently when refactoring a Vue 1.0 application, I utilized ES6 arrow functions to clean up the code and make things a bit more consistent before updating to Vue 2.0. Along the way I made a few mistakes and wanted to share the lessons I learned as well as offer a few conventions that I will be using in my Vue applications moving forward.

The best way to explain this is with an example so lets start there. I'm going to throw a rather large block of code at you here, but stick with me and we will move through it a piece at a time.

<script>

// require vue-resource...

new Vue({
  
  data: {
      item: {
        title: '',
        description: '',
      }
  },
  
  methods: {
  
    saveItem: function() {
    
      let vm = this;
      
      this.$http.post('item', this.item)
        .then(
        
          function (response) {
            vm.item.title = '';
            vm.item.description = '';
          }, 
          
          function (response) {
            console.log('error', response);
          }
          
        );
    }
    
  }
});
</script>

The contrived code sample above would allow you to fill out a small form, and then submit that form to persist a new item to a database. Although this is pretty basic, there are still a few things that I feel could be cleaned up.

Arrow Functions, and lexical this

Lets start by looking at the saveItem() method.

...

saveItem: function() {

let vm = this;

this.$http.post('item', this.item)
  .then(
  
    function (response) {
      vm.item.title = '';
      vm.item.description = '';
    }, 
    
    function (response) {
      console.log('error', response);
    }
    
  );
}

...

Something that has always bothered me is the need to assign a temporary variable to hold the value of this. The point of assigning vm = this is so we can later reference vm to get our Vue object. Wouldn't it be nice if we could somehow inherit this in those later anonymous functions without having to place it in a temp variable? Thanks to ES6 arrow functions we can do exactly that.

When we use an arrow function, the this is lexical, meaning that it does not create its own this context. Instead, this has the original meaning from the enclosing context. That means that we can replace our function (response) {} callbacks with a much prettier and more terse ES6 arrow function and skip setting up that temporary variable to hold the reference to the Vue object.

...

saveItem: function() {

  // let vm = this;
  
  this.$http.post('item', this.item)
   .then(

    //function (response) => {
    
    response => {
     this.item.title = '';
     this.item.description = '';
    }, 

    //function (response) => {
    
    response => {
     console.log('error', response);
    }
     
   );
}

...

Looking better already! Lets keep going.

Arrow function overkill

If one arrow function is good, more of them must be better right? I mean who doesn't enjoy yanking every single function() {} out of their codebase and replacing it with a simple () => {}. Looking again at the saveItem() method, we could rewrite that using an arrow function to look like this.

...
methods: {
  saveItem: () => {
    this.$http.post('item', this.item)
      .then(
        // callbacks in here
      );
  }
}
...

Perfect! Now we have ridded our self of the dreaded function and replaced it with our shiny new arrow function syntax. But wait, theres a catch.

Since arrow functions provide a lexical this value, the this inside our saveItem() refers to the window instead of our Vue object which breaks our current implementation! When attempting to get this.item, we will actually be looking at window.item which is currently undefined.

If only there were another way!

Method Definitions

As explained over at MDN, method definitions are shorthand for a function assigned to a method name. Given the following code:

var obj = {
  foo: function() {},
  bar: function() {}
};

You are now able to shorten this to:

var obj = {
  foo() {},
  bar() {}
};

Applying that to our saveItem() method, we can shorten the definition without having to worry ourselves with that lexical this binding that the arrow function was causing.

...
methods: {
  saveItem() {
    this.$http.post('item', this.item)
      .then(
        // callbacks in here
      );
  }
}
...

In case it isn't clear, this works for any "top level" functions that are assigned to object keys in our Vue object. You might consider using this for created or data functions.

Speaking of data

In our current code, our data key is associated with a plain Javascript Object. However, if you have worked with Vue components, you may be aware that when defining a component it is necessary to wrap the returned object in a closure. The reason for this is explained in the the docs or this blog post by Jeff Madsen, but let me just show you what it looks like for now.

...
data: function() {
  return {
    item: {
      title: '',
      description: '',
    }
  }
},
...

This is all fine and dandy, but it turns out there is a way to use arrow functions to clean this up a bit. We have already learned that arrow functions provide us with a lexical this binding, but they also provide us some options when defining our function body. In our previous examples we have used "block body" syntax. The second option we have is to provide a "concise body". Let me show you both together so you can see the difference.

var sum = (a,b) => {return a+b;}  // block body syntax, explicit "return" needed
var sum = (a,b) => a+b;           // concise body, implied "return"

var sum = (a,b) => ({sum: a+b});  // returning an object literal requires ()

As you can see, if our function is just returning a value, we can exclude the {} and return and instead just write our return statement. In the last example you can see how returning an object literal has one additional requirement which is a set of (). Let's try to apply this to our data closure.

// before
data: function() {
  return {
    item: {
      title: '',
      description: '',
    }
  }
},

// after
data: () => ({
    item: {
      title: '',
      description: '',
    }
}),

It's a small improvement, but I like the way it looks. Of course you could also use method definition style as well.

// method definition style
data() {
  return {
    item: {
      title: '',
      description: '',
    }
  }
}

Vue ES6 Conventions

With this new found knowledge, I have been using the following conventions when defining my Vue modules.

  1. Use method definitions for all "top level" methods.
  2. Use arrow functions for any callbacks inside "top level" methods.
  3. Use an arrow function with a "concise body" for component data closures.

Hopefully these little tips will make writing your Vue modules and components that much more enjoyable and readable. Thanks!

Footnotes

https://rainsoft.io/when-not-to-use-arrow-functions-in-javascript/

Created 11 months ago | Updated 1 day ago

Comments (5)

There's some great info in here to tidy up my Vue code which I wasn't previously thinking about, thanks!

Be careful with this notation data: () => ({ }) You can not use this to access the component instance

In Vue 2+, is this valid?

Don’t use arrow functions on an instance property or callback (e.g. vm.$watch('a', newVal => this.myMethod())). As arrow functions are bound to the parent context, this will not be the Vue instance as you’d expect and this.myMethod will be undefined.

source: https://vuejs.org/v2/guide/instance.html#Properties-and-Methods

@jsonberry that is valid. All arrow functions inherit their parent's scope. So this references the parent, not the current functions...

May I ask which convention is being used here in the docs when calling the getAnswer method? it does not appear to use the arrow method or have a function keyword. I am trying to accomplish something similar and I had trouble getting it working since I was using the wrong syntax. I'm assuming it has something to do with lodash returning the wrapped function instance?