🥐

最小限の構成でVue.extendとクラスコンポーネントを比べてみる

2021/06/27に公開

Vue.jsでTypeScriptを使う時、いくつか選択肢があるのを知って整理したくなったので最小限の構成で同じ機能のものを作成し見比べて見ました。

Vueのバージョンは2.x系です。

なお、Vueの3系では、クラスコンポーネント以外にComposition APIといった新しい書き方が主流になりつつありますが、2.x系を使用したプロジェクトはまだ沢山ある、現在携わっているプロジェクトもクラスコンポーネントを使用している為、TypeScriptの理解を深めるためにもVue.extendとクラスコンポーネントの比較をしています。

検証する


ボタンをクリックすると、数字がカウントアップします。
10回クリックするとコンソールが表示されリセットします。

確かめられます。
https://kawa-t.github.io/vue_class_style_typescript/

リポジトリはこちら。
https://github.com/kawa-t/vue_class_style_typescript

構成はなるべくシンプルに

ファイル構成
./src
├── components
│   ├── AddButton.vue
│   ├── AddButtonExtend.vue
│   ├── PlayGround.vue
│   ├── PlayGroundExtend.vue
│   ├── CountNumber.vue
│   └── CountNumberExtend.vue
└── App.vue

どちらの機能も全く同じですが、Vue.Extendで記述したものはファイル名の後ろにExtendをつけています。

Vue.extendとクラスコンポーネントとは

Vue.jsでTypeScriptを使うときに選択肢となるのは、Vue.extendベースを適用するか、クラスコンポーネントを適用するかの2択になります。
ざっくり言うと、Vue.extendベースではTypeScriptを導入していないと場合とほとんど変わりませんが、クラスコンポーネントではかなり大きく書き方が変わるのが特徴です。

VueCLIで環境構築する

Vue.js×TypeScriptを試す際に一番気軽なのが、Vue CLiを利用することです。
コマンドラインを使って会話形式で簡単にVue×TypeScriptの環境構築ができます。
コマンドラインでvue create <プロジェクト名>をすると、会話が始まります。

Manually select featuresを選びます。

? Please pick a preset: 
  Default ([Vue 2] babel, eslint) 
  Default (Vue 3 Preview) ([Vue 3] babel, eslint) 
❯ Manually select features 

Vue.extend、クラスコンポーネントどちらの場合でも、TypeScritを選ぶのを忘れずに選択します。(スペースで選択できます。)

? Check the features needed for your project: 
 ◉ Choose Vue version
 ◉ Babel
❯◉ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

今回は2.xを選択します。

? Choose a version of Vue.js that you want to start the project with 
❯  2.x 
   3.x (Preview) 

下記の質問で、y(yes)を選択すると、クラスコンポーネントの開発になります。
クラスコンポーネント開発するときは、yを選びます。
なお、クラスコンポーネントを選んでも、Vue.extendで書くことはできるので、著者はyを選択肢し両方の書き方を行っています。

? Use class-style component syntax? (y/N)  

他にいくつか選択項目がいくつか出てきますが、任意の項目を選んでしまって大丈夫です。

Vue.extendベースでの開発

基本となる書き方です。

Vue.extendベースの基本形

<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
});
</script>

script lang="ts"として、vueをインポートします。
export default Vue.extendの中に処理を書きます。
Vue.extendベースでのメリットは、TypeScriptを導入していない場合の書き方と大きくは変わらない点です。

  • Vueを使っているけどTypeScriptを導入した事がない。
  • 今までと書き方が変わってしまうのは抵抗がある。

など、TypeScriptに慣れていない・初めて導入する際はこちらの方が分かりやすいです。

下記の様にdataやmethodsも同じ様に書くことができます。
TypeScriptの醍醐味、型注釈はそれぞれのメソッド事にするイメージです。

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
   name: "",
  components: {
  },
  data() {
    return {
    };
  },
  methods: {
  },
  watch: {
  },
});
</script>

クラスコンポーネントベースでの開発

クラスコンポーネントベースでの開発でも、VueCLIを使って環境構築します。
VueCLIを使うと、下記の選択肢でクラスコンポーネントを適用しますか?と聞かれます。
クラスコンポーネントを使用するときは、下記の質問でyesを選びます。

? Use class-style component syntax? (y/N)  

ここでyesを選択することで、必要なデコレータなどをインストールしてくれます!

クラスコンポーネントベースの基本形

<script lang="ts">
import Component from "vue-class-component";
import Vue from "vue-property-decorator";

@Component
export default class extends Vue {}
</script>

script lang="ts"としているのは、vue.extendと同じです。
クラスコンポーネントでは、vue-class-componentとvue-property-decoratorをインポートして@Component(デコレータ)と書いて、そのなかにcomponentsを書きます。

クラスコンポーネントで書くことのメリットは、クラス構文を使用するのでコード量がVue.extendを使った時よりも少なくなる、機能ごとにdataやmethodをまとめる書き方ができるので、見通しがよくなるなどのメリットがあります。(この辺りは意見が分かれやすくて人それぞれのイメージがあります。)

それぞれの違い

dataプロパティ

Vue.extendの場合

CountNumberExtend.vue
export type DataType = {
  count_extend: number;
};

export default Vue.extend({
  data():DataType {
    return {
      count_extend: 0,
    };
  },
});

クラスベースの場合

CountNumber.vue
export default class App extends Vue {
  public count = 0;
}

クラスベースでは、プロパティとして定義します。

methodsプロパティ

Vue.extendの場合

CountNumberExtend.vue
export default Vue.extend({
  methods: {
    countupExtend: function(): void {
      this.count_extend++;
    },
  },
});

クラスベースの場合

CountNumber.vue
import Component from "vue-class-component";
@Component({

})
//Componentからは外出しして、クラスを定義
export default class App extends Vue {
  public countUp(): void {
    this.count++;
  }
}

vue.extendで書く場合、TypeScriptを導入していない場合とでは、型推論があるかないかの違いです。
countupExtendメソッドに戻り値がない事を示すvoidがついているくらいです。
一方クラスベースでは、クラスのメソッドとして外出しします。

Computedプロパティ

Vue.extendの場合

PlayGoundExtend.vue
export default Vue.extend({
  computed: {
    doubleCount_exted(): number {
      return this.count_extend * 2;
    },
  },
});

クラスベースの場合

PlayGound.vue
import Component from "vue-class-component";

@Component
export default class PlayGound extends Vue {
  public get doubleCount(): number {
    return this.count * 2;
  }
}

クラスベースではpublic getとしている部分がポイントです。
vue-class-componentをインポートして、@Componentの中に書くことによってgetはgetterメソッドとして定義されます。
getterメソッドはcomputedとして扱えるため、computedメソッドを使う時はgetを使います。

watchプロパティ

Vue.extendの場合

CountNumberExtend.vue
<template>
  <div>
    <PlayGound :count="count"></PlayGound>
  </div>
</template>
<script lang="ts">
export default Vue.extend({
  watch: {
    count_extend: function(CountNumerEx: number): void {
      if (CountNumerEx > 10) {
        alert("EndGame");
        this.count_extend = 0;
      }
    },
  },
});
</script>

クラスベースの場合

CountNumber.vue
<template>
  <div>
    <PlayGoundEx :count_extend="count_extend"></PlayGoundEx>
  </div>
</template>
<script lang="ts">
import Component from "vue-class-component";
import { Vue, Watch } from "vue-property-decorator";

@Component({})
export default class App extends Vue {}
  @Watch("count")
  public finishCount(CountNumer: number): void {
    if (CountNumer > 10) {
      alert("EndGame");
      this.count = 0;
    }
  }
}
</script>

watchを使う時は、vue-property-decoratorのメソッド(Watch)をインポートします。
@Component中では書かずに、外出しすることでwatchプロパティを使います。

propsプロパティ

Vue.extendの場合

PlayGoundExtend.vue
export default Vue.extend({
  props: {
    count_extend: Number,
  },
});

クラスベースの場合

PlayGound.vue
import Component from "vue-class-component";
import { Prop, Vue } from "vue-property-decorator";
@Component({
  @Prop()
  public count!: number;
})

propsをクラスベースで使う時は、Propsデコレータをインポートします。
countの後ろに!マークがありますが、これは値が必ず設定されていることを保証するものです。
設定されていない場合もありうる場合は、?マークを設定します。

emitプロパティ

Vue.extendの場合

AddButtonExtend.vue
<template>
  <div>
    <button @click="pushExtend">Click Button(Extend.ver)</button>
  </div>
</template>
<script lang="ts">
import Vue from "vue";

export default Vue.extend({
  methods: {
    pushExtend(): void {
      this.$emit("push_extend");
    },
  },
});
</script

クラスベースの場合

AddButton.vue
<template>
  <div>
    <button @click="push">Click Button</button>
  </div>
</template>
<script lang="ts">
import Component from "vue-class-component";
import { Emit, Vue } from "vue-property-decorator";

@Component
export default class AddButton extends Vue {
  @Emit("push")
  public push(): void {
    // no return
  }
}
</script>

emitはpropsと同じように、デコレータの@Emitを使用します。
Vue.extendではmethodsの中にthis.$emit("push_extend");
というように記述しますが、クラスベースでは@Emitとして別のメソッドにします。

どちらを選択するか

Vue.extendの場合は、現行のプロジェクトからほとんどスタイルを変えることなく、型注釈をすることに専念できるため、初めてTypeScriptをVueに導入する場合はVue.extendでTypeScriptに慣れておくのがいいかなと思います。

ただ、クラスコンポーネントを採用している場合もあるので知っておいて損はないかと思います。

個人的には、dataはdata、methodsはmethodsで一塊になっていた方が全体的にスッキリした形になるので、Vue.extendを使って最初に慣れておく事がいいのかなと思いました。

Discussion