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.
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.
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!
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.
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: '',
}
}
}
With this new found knowledge, I have been using the following conventions when defining my Vue modules.
- Use method definitions for all "top level" methods.
- Use arrow functions for any callbacks inside "top level" methods.
- 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!
https://rainsoft.io/when-not-to-use-arrow-functions-in-javascript/
You can, but need do it
data: self => ({ name: self.$store.getters.. })