VueコンポーネントのfunctionをES6スタイルで書く

# はじめに  
Vueでは関数定義をES6のアロー関数（Arrow Function）を使わずにES5以前からの書き方のfunction式（function expression）を使う方法が[推奨されています](https://jp.vuejs.org/v2/api/#methods)。  
  
>メソッド(例 plus: () => this.a++) を定義するためにアロー関数を使用すべきではないことに注意してください。
アロー関数は、this が期待する Vue インスタンスではなく、this.a が undefined になるため、親コンテキストに束縛できないことが理由です。
  
```javascript
var vm = new Vue({
  data: { a: 1 },
  methods: {
    plus: function () {
      this.a++ // this = VueComponent
    }
  }
})
vm.plus()
vm.a // 2
```
  
例えばmethods内部でfunctionを定義した場合、thisに呼び出し元のVueインスタンスが格納されます。  
  
```javascript
var vm = new Vue({
  data: { a: 1 },
  methods: {
    plus: () => {
      this.a++ // this = undefined
    }
  }
})
vm.plus()
vm.a
```
一方でアロー関数で定義した場合、thisの内容はundefinedとなります。
そして上記の例でのthis.aの呼び出しはundefinedのプロパティを参照しようとするのでエラーとなってしまうのです。  


これは従来のfunction expressionが呼び出し元のコンテキストをthisに格納する挙動に対して、アロー関数ではthisが呼び出し元ではなくレキシカルスコープのthisにバインドされるという違いによるものです。

私も当初アロー関数に書き直そうとして、このthis参照問題に当たってしまいました。
GoogleやQiitaで検索してみるとアロー関数を諦めてfunctionで関数定義しなさいと解説している記事がいくつかヒットするので同じ悩みの人が一定数いるようです。  
  
しかし別の記法でES6スタイルで記述されている方のgistを参考に、functionを撤廃することができたので具体的な方法を紹介します。  
  
# 要約
  
ES6からObjectのpropertyにfunctionを定義する場合の短縮構文である、メソッド定義構文（Method Definition）が実装されました。
この記法を使うことでVueのcomputed, methodsなどの内部で定義したfunctionを置き換えていきます。
[メソッド定義 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Method_definitions)

```javascript
var obj = {
  foo: function() {
    /* コード */
  },
  bar: function() {
    /* コード */
  }
};

var obj = {
  foo() {
    /* コード */
  },
  bar() {
    /* コード */
  }
};
```
  
なぜアロー関数ではなくこの記法にするのかは後述します。  
  
それでは個別の事例ごとにNGパターンを踏まえつつES6スタイルに書き直してみましょう。  
  
  
# props
  
propsなどの内部のプロパティとして値を返すfunctionが定義されている場合  

```javascript
props: {
  colorCodes: {
    type: Array,
    default: function() {
      return ['#FFFFFF', '#F0F0F0'];
    }
  }
}
```
  
これは単純にアロー関数にできそうですね？
  
```javascript
props: {
  colorCodes: {
    type: Array,
    default: () => {
      return ['#FFFFFF', '#F0F0F0'];
    },
  },
},
```
  
できました！しかし…
functionの内部でthisが参照されている時はどうでしょうか？  
  
```javascript
props: {
  colorCodes: {
    type: Array,
    default: function() {
      console.log(this); // VueComponentが参照できる
      return this.colorArray;
    },
  },
}
```

####NGな書き方
前述のように単純にアロー関数に変換すると内部にthisが記述された時に参照できなくなってしまいます  

```javascript
props: {
  colorCodes: {
    type: Array,
    default: () => {
      console.log(this); // undefined
      return this.colorArray; // undefinedのプロパティを参照しようとしてエラーになる
    },
  },
}
```
  
####OKな書き方
メソッド定義形式で書けば内部でthisが参照できます

```javascript
props: {
  colorCodes: {
    type: Array,
    default() {
      console.log(this); // VueComponentが参照できる
      return this.colorArray;
    },
  },
}
```

# computed, methodsなど
computedの内部でfunctionで定義されている場合

```javascript
computed: {
  mySomething: function() {
    const something = this.getSomething();
    ...
  },
},
```
####NGな書き方
単純にアロー関数に書き換えると、内部にthisが記述された時に参照できなくなります

```javascript
computed: {
  mySomething: () => {
    const something = this.getSomething(); // undefinedのプロパティを参照しようとしてエラーになる
    ...
  },
},
```
####OKな書き方
メソッド定義形式で書けば内部でthisが参照できます

```javascript
computed: {
  mySomething() {
    const something = this.getSomething(); // thisでVueComponentが参照できる
    ...
  },
},
```
  
computedに限らずmethodsなどの中身も同様にメソッド定義形式に書き換えられます

```javascript
methods: {
  getColorCodes: function() {
    return {
      startColor: this.colorCodes[0],
      endColor: this.colorCodes[1],
    };
  }
},
```
####OKな書き方

```javascript
methods: {
  getColorCodes() {
    return {
      startColor: this.colorCodes[0],
      endColor: this.colorCodes[1],
    };
  }
},
```
# 実例
Nuxtのサイトのサンプルがメソッド定義形式で書かれていました
[Vuex ストア - NuxtJS](https://ja.nuxtjs.org/examples/vuex-store)

```javascript
export default {
  fetch ({ store }) {
    store.commit('increment')
  },
  computed: mapState([
    'counter'
  ]),
  methods: {
    increment () {
      this.$store.commit('increment')
    }
  }
}
```

# 実例2
element-uiのサンプルもメソッド定義形式で書かれていました
[Element - The world's most popular Vue UI framework](https://element.eleme.io/#/en-US/component/collapse)

```vuejs
<el-collapse v-model="activeNames" @change="handleChange">
  ... 省略 ...
</el-collapse>
<script>
  export default {
    data() {
      return {
        activeNames: ['1']
      };
    },
    methods: {
      handleChange(val) {
        console.log(val);
      }
    }
  }
</script>
```

# ESLint Rule（追記）
ESLintのObject-Shorthandルールでこの記法を強制することができます
[object-shorthand - Rules - ESLint - Pluggable JavaScript linter](https://eslint.org/docs/rules/object-shorthand)

```json
{
  "object-shorthand": ["error", "always"]
}
```
ルールのページに書かれてますが、オブジェクトのプロパティとしてアロー関数を記述した場合はこのルールを入れても警告が出ないのでそこは注意しましょう
> This rule does not flag arrow functions inside of object literals. The following will not warn:

```javascript
/*eslint object-shorthand: "error"*/
/*eslint-env es6*/

var foo = {
  x: (y) => y
};
```

# 参考サイト  
  
超素晴らしい解説のgistです。ほぼこれを参考にしています。
[Clean up your Vue modules with ES6 Arrow Functions · GitHub](https://gist.github.com/JacobBennett/7b32b4914311c0ac0f28a1fdc411b9a7)
  
MDN公式のメソッド定義（Method definitions）リファレンス
[メソッド定義 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Method_definitions)
  
本記事は個人ブログに投稿したものをベースにリライトしました
https://pisolino.hatenablog.com/entry/2020/01/24/020059