Skip to content

Instantly share code, notes, and snippets.

@JacobBennett
Last active September 4, 2024 18:48
Show Gist options
  • Save JacobBennett/7b32b4914311c0ac0f28a1fdc411b9a7 to your computer and use it in GitHub Desktop.
Save JacobBennett/7b32b4914311c0ac0f28a1fdc411b9a7 to your computer and use it in GitHub Desktop.
Clean up your Vue modules with ES6 Arrow Functions

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/

published: true
preview: 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. 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.
@papalardo
Copy link

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

You can, but need do it data: self => ({ name: self.$store.getters.. })

@ctessmer
Copy link

Thank you so much for this man! I'm new to Vue, and was having an impossible time trying to affect my Vue data parameters from within Vue methods. I found this gem and this sprung to life. Works perfect. Really appreciate it!

@achhunna
Copy link

Nice explanation on why using arrow function for methods in Vue isn't a good idea. Compare this to how component functions are written in React, where arrow functions help to bind this context on the execution of the class/constructor function. So someone coming from React to Vue might get confused at first.

@code100
Copy link

code100 commented Feb 26, 2020

Great explanation! you really save my day!

@alexthecurator
Copy link

Can you use arrow functions in setup() ? Eg:

setup(){
  const subscribe = () => {
           //Something here
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment