• home
  • Vueのコンテキストを侵しつつTypeScriptの型を効かせる

Vueのコンテキストを侵しつつTypeScriptの型を効かせる

伏見です。
ここ数日、稼働中プロジェクトの開発者向け画面でvueを試す機会があり、ついでにフロント周りでクリーンアーキテクチャしてみたりTypeScriptとvueのテンプレ記法周りの相変わらずの相性の悪さにヤキモキしたりしています。

ちょっとVuexやユースケース層/アクション発行層へのアクセス について覚えたことがあったのでメモがてら書きます。

共通処理や共通インスタンスのアクセス

コンポーネントとロジック部分の接続部分でどうTypeScriptの型の恩恵を効かせていこうな?という模索があり、とりあえず思いついたのは次の2パターン。

(1) 普通にシングルトンや個別に共通モジュールを書いて型を効かせる

let allStoreState : AllStoreState
export function getAllStoreState() AllStoreState {
  return allStoreState;
}

各層の構成の理解が共有できれば「必要に応じてモジュールimportする」この形が正しいかもしれません。
大規模で推奨されるのは基本的にこの構成とされています。
Vue.useですら少しモヤッとする。

(2) Vueを継承した共通コンテナコンポーネントを作って上位コンポーネントでは必ずそれを使うようにする

@Component
export default class BaseContainerComponent extends Vue {

   getAllStoreState() : AllStoreState {
      return this.$store.state as AllStoreState;
   }
}

なんとなくだが継承は避けたいです。クラスコンポーネントを使っていくのか?という話もあり。

(3) TypeScriptの定義ファイルを使って雑にVue. prototypeを汚染しつつ型を効かせる

ここからは新しく覚えたこと。
「新しく自分のVueのcontextに沿った定義ファイル(d.ts)を作る」というやり方です。

依存の初期化として、axios等を刺す一番雑なパターンとして紹介されている Vue.prototype.$xxx = xxx をvueアプリの初期化時点でいきなり使います。

エントリーポイント(に近いjs/ts)ファイル

...

Vue.prototype.$actionCreators = {
  FirstActionCreator: new MyActionCreator(allUsecase, allStore),
  SecondActionCreator: new MyActionCreator(allUsecase, allStore)
};

Vue.prototype.$snackBar = {
  success: (message: any) => {
    Snackbar.show({
      text: message,
      showAction: false,
      duration: 1000,
      pos: "bottom-center",
      backgroundColor: "#107e2d"
    });
  },
  danger: (message: any) => {
    Snackbar.show({
      text: message,
      showAction: false,
      duration: 1000,
      pos: "bottom-center",
      backgroundColor: "#7e3534"
    });
  }
};

Vue.prototype.$getAllStoreState = function() {
  return this.$store.state;
};

雑に魔法を刺します。

ここで、TypeScriptの定義ファイルをビルドスコープ内に追加で置いておき、上記の魔法の型のインターフェースを記載します。

customContext.d.ts

import Vue from "vue";
import Vuex, { Store } from "vuex";
import { AllStoreState } from "@/stores/allStore";

import { IFirstActionCreator } from "@/actionCreators/firstActionCreator";
import { ISecondActionCreator } from "@/actionCreators/seconedActionCreator";

// .vueファイルで "this.$actionCreatorsや this.getAllStoreState()" で型が効くようにする。
declare module "vue/types/vue" {
  interface Vue {
    $actionCreators: {
      FirstActionCreator: IFirstActionCreator;
      SecondActionCreator: ISecondActionCreator
    };
    $snackBar: {
      danger: (message: string) => void;
      success: (message: string) => void;
    };
    $getAllStoreState: () => AllStoreState;
  }
}

これで、各コンポーネントから this.$actionCreators や this.$snackBar にアクセスしても、Typescriptの型の恩恵やIDE支援が受けられます。

Vueは色々な書き方を許容しますが、「ちょうどいい温度感」はチームやケースによって異なるので、少しばかり模索が必要そうですね。