LoginSignup
52

More than 5 years have passed since last update.

Vue.js+TypeScriptのススメ

Last updated at Posted at 2019-03-03

はじめに

いつもと違う環境でアプリを作りたくなり、Vue.js+TypeScriptでアプリを作ってみました。
Vueのみ or TypeScriptのみの情報は結構見つかりましたが、Vue+TypeScriptについてがっつり書いてるものが無かったので自分で記事を書いてみることにしました。
この記事では分かりやすいようにコードを比較する形で書いてます。
※JavaScriptのコード例はTypeScriptから逆算して書いたので間違ってるかもしれません。その際は指摘してもらえると助かります。

勧める理由

VueとTypeScript(正確にはvue-class-componentvue-property-decoratorも含む)を併用することで、型システムやデコレーター等を用いて型安全なコードを簡潔に書けるためです。TypeScriptはいいぞ。

作ったもの

今回はCoD(FPSの洋ゲー)の戦績閲覧アプリをElectronで作りました。
アカウントを入力すると戦績が表示されます。モード毎の戦績とマップごとの戦績があり、モードやマップを絞り込んだり戦績順にソートしたりできます。また下にはサマリ行があり、カーソルを重ねると円グラフがポップアップします。

TypeScriptでのVueクラスの書き方

まずは(トランスパイラを使わない)JavaScriptとTypeScriptを比較してみましょう。以下は単一ファイルコンポーネントのスクリプト部分です。

まずはJavaScript。

module.exports = {
  // 型と初期値を持つprops
  props: {
    propMessage: {
      type: String,
      default: ""
    }
  }

  data: function() {
    return {
      message: 123 // 初期データ
    }
  }

  // 算出プロパティ
  computed: {
    computedMessage: function() {
      return 'computed ' + this.message;
    }
  }

  // 監視プロパティ
  watch: {
    message: {
      handler: function(newMessage, oldMessage) { /*処理*/ },
      immediate: true,
      deep: true
    }
  }

  // ライフサイクルフック
  mounted: function() {
    this.greet();
  }

  // 普通のメソッド
  methods: {
    greet: function() {
      alert('greeting: ' + this.message);
    }
  }
}

次にTypeScript。

import Vue from 'vue'
import Component from 'vue-class-component'

@Component({
  // 型と初期値を持つpropsと監視プロパティ
  props: {
    propMessage: {
      type: String,
      default: ""
    }
  },
  watch: {
    "message": [
      {
        handler: "onMessageChanged",
        immediate: true,
        deep: true
      }
    ]
  },
  methods: {
    onMessageChanged(newMessage: string, oldMessage: string): string { /*処理*/ }
  }
})
export default class App extends Vue {
  // 初期データをクラスのプロパティとして記述できる
  message: string = 123;

  // 算出プロパティは読み取り専用プロパティとして記述できる
  get computedMessage(): string => 'computed ' + this.message;

  // ライフサイクルフックをクラスメソッドとして記述できる
  mounted(): void => this.greet();

  // 普通のメソッドもクラスメソッドとして記述できる
  greet(): void => alert('greeting: ' + this.message);
}

上の例で分かるかもしれませんが、JavaScriptでは以下のような制約がありコードを簡潔に書けません。

  • dataをメソッドの戻り値として定義する必要がある
  • watchmethods、ライフサイクルフック(mounted等)でアロー演算子が非推奨

TypeScriptではdataはクラスのプロパティで書けますし、アロー演算子も使えるため簡潔にコードを書けます。更にプロパティ・メソッドの引数や戻り値に型を指定することができ、型安全にコードを書けます。

Vue Property Decoratorを用いた記述

TypeScriptはJavaScriptと比較して簡潔にコードが書けることがわかったかと思いますが、propswatchが少し冗長と思ったかも知れません。それを解決するのがvue-property-decoratorです。
vue-property-decoratorを導入することで、propswatchは以下のように書き換えることが出来ます。

@Component({
  props: {
    propMessage: {
      type: String,
      default: ""
    }
  },
  watch: {
    "message": [
      {
        handler: "onMessageChanged",
        immediate: true,
        deep: true
      }
    ]
  },
  methods: {
    onMessageChanged(newMessage, oldMessage): string { /*処理*/ }
  }
})
@Prop({ default: "" }) propMessage!: string
@Watch("Message", { immediate: true, deep: true })
onMessageChanged(newMessage: string, oldMessage: string): string { /*処理*/ }

これでpropswatchがかなり簡潔に書けました。20行のコードが3行になりました。

まとめ

最後に、改めてJavaScriptとTypeScriptのコード全体を比較してみましょう。
まずJavaScript。

module.exports = {
  // 型と初期値を持つprops
  props: {
    propMessage: {
      type: String,
      default: ""
    }
  }

  data: function() {
    return {
      message: 123 // 初期データ
    }
  }

  // 算出プロパティ
  computed: {
    computedMessage: function() {
      return 'computed ' + this.message;
    }
  }

  // 監視プロパティ
  watch: {
    message: {
      handler: function(newMessage, oldMessage) { /*処理*/ },
      immediate: true,
      deep: true
    }
  }

  // ライフサイクルフック
  mounted: function() {
    this.greet();
  }

  // 普通のメソッド
  methods: {
    greet: function() {
      alert('greeting: ' + this.message);
    }
  }
}

次にTypeScript。

import Vue from 'vue'
import { Component, Vue, Prop, Watch } from "vue-property-decorator";

@Component
export default class App extends Vue {
  @Prop({ default: "" }) propMessage!: string
  @Watch("Message", { immediate: true, deep: true })
  onMessageChanged(newMessage: string, oldMessage: string): string { /*処理*/ }

  // 初期データをクラスのプロパティとして記述できる
  message: string = 123;

  // 算出プロパティは読み取り専用プロパティとして記述できる
  get computedMessage(): string => 'computed ' + this.message;

  // ライフサイクルフックをクラスメソッドとして記述できる
  mounted(): void => this.greet();

  // 普通のメソッドもクラスメソッドとして記述できる
  greet(): void => alert('greeting: ' + this.message);
}

比較しても分かる通りTypeScriptではコードを簡潔に、しかも型安全に書けます。
Vue+TypeScriptのススメ、お分かりいただけただろうか?

おまけ

フォームの内容を型安全にやりとりする方法

フォームを実装する際、簡単なStoreパターンを用いて内容を外部とやりとりしたいと思いました。その際、フォームの内容を型安全にやり取りしたいと思って試行錯誤した結果、以下のやり方に落ち着いたので紹介しておきます。
やり方はフォームの内容を表すクラスを定義し、フォームの初期値をプロパティの初期値として定義することです。こうすることでそのクラスのインスタンスを渡すことでフォームに初期値が設定できます。

  import { Component, Vue, Prop } from "vue-property-decorator";

  export class InqueryFormFields {
    platform: string = "psn";
    id: string = "";
  }

  @Component
  export default class InqueryForm extends Vue {
    @Prop(InqueryFormFields) formInitialValues!: InqueryFormFields

    // formInitialValuesが存在する場合はformへ値渡しする
    form = this.formInitialValues ? Object.assign(new InqueryFormFields(), this.formInitialValues) : new InqueryFormFields();

    onSubmit(): void => this.$emit("submit", Object.assign(new InqueryFormFields(), this.form));
  }

(殆どのフォームがそうかと思いますが)上記の例ではInqueryFormFieldsインスタンス参照を渡すと意図しない挙動になるので、Object.assign()InqueryFormFieldsインスタンスをコピーしています。
外部から渡されたインスタンスの値をコピーするのはInqueryFormの責務なので、InqueryForm内に実装を隠蔽する形にしています。こうすることで外部のクラスがわざわざ値をコピーする必要がなくなります

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
52