pisolino's blog

お仕事でハマったところの覚え書きとか、趣味とか。

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

はじめに

Vueでは関数定義をES6のアロー関数(Arrow Function)を使わずにES5以前からの書き方のfunction式(function expression)を使う方法が推奨されている

メソッド(例 plus: () => this.a++) を定義するためにアロー関数を使用すべきではないことに注意してください。 アロー関数は、this が期待する Vue インスタンスではなく、this.a が undefined になるため、親コンテキストに束縛できないことが理由です。

var vm = new Vue({
  data: { a: 1 },
  methods: {
    plus: function () {
      this.a++ // this = VueComponent
    }
  }
})
vm.plus()
vm.a // 2

例えばmethods内部でfunctionを定義した場合、thisに呼び出し元のVueインスタンスが格納される。

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

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

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

なぜアロー関数ではなくこの記法にするのかは後述。

それでは個別の事例ごとにNGパターンを踏まえつつES6スタイルに書き直してみたい。

props

propsなどの内部のプロパティとして値を返すfunctionが定義されている場合

props: {
  colorCodes: {
    type: Array,
    default: function() {
      return ['#FFFFFF', '#F0F0F0'];
    }
  }
}

これは単純にアロー関数にできそうだ?

props: {
  colorCodes: {
    type: Array,
    default: () => {
      return ['#FFFFFF', '#F0F0F0'];
    },
  },
},

できた!しかし… functionの内部でthisが参照されている時はどうかな?

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

NGな書き方

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

props: {
  colorCodes: {
    type: Array,
    default: () => {
      console.log(this); // undefined
      return this.colorArray; // undefinedのプロパティを参照しようとしてエラーになる
    },
  },
}

OKな書き方

メソッド定義形式で書けば内部でthisが参照できる

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

computed, methodsなど

computedの内部でfunctionで定義されている場合

computed: {
  mySomething: function() {
    const something = this.getSomething();
    ...
  },
},

NGな書き方

単純にアロー関数に書き換えると、内部にthisが記述された時に参照できなくなる

computed: {
  mySomething: () => {
    const something = this.getSomething(); // undefinedのプロパティを参照しようとしてエラーになる
    ...
  },
},

OKな書き方

メソッド定義形式で書けば内部でthisが参照できる

computed: {
  mySomething() {
    const something = this.getSomething(); // thisでVueComponentが参照できる
    ...
  },
},

また、computedに限らずmethodsなどの中身も同様にメソッド定義形式に書き換えられる

methods: {
  getColorCodes: function() {
    return {
      startColor: this.colorCodes[0],
      endColor: this.colorCodes[1],
    };
  }
},

OKな書き方

methods: {
  getColorCodes() {
    return {
      startColor: this.colorCodes[0],
      endColor: this.colorCodes[1],
    };
  }
},

実例

Nuxtのサイトのサンプルがメソッド定義形式で書かれていた
Vuex ストア - NuxtJS

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

<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

{
  "object-shorthand": ["error", "always"]
}

ルールのページに書かれているが、オブジェクトのプロパティとしてアロー関数を記述した場合はこのルールを入れても警告が出ないのでそこは注意

This rule does not flag arrow functions inside of object literals. The following will not warn:

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

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

参考サイト

超素晴らしい解説のgistです。ほぼこれを参考にしています。
Clean up your Vue modules with ES6 Arrow Functions · GitHub

MDN公式のメソッド定義(Method definitions)リファレンス
developer.mozilla.org

Vue.js入門 基礎から実践アプリケーション開発まで

Vue.js入門 基礎から実践アプリケーション開発まで

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発

  • 作者:花谷 拓磨
  • 出版社/メーカー: シーアンドアール研究所
  • 発売日: 2018/10/17
  • メディア: 単行本(ソフトカバー)