TypeScriptでVuexを使うときのスタンダードっぽいやりかたを調べてみた

この記事は [:christmas_tree:オープンストリーム Advent Calendar 2018:christmas_tree:](https://qiita.com/advent-calendar/2018/opst) の11日目の記事です。
うっかり期限を勘違いしていて前日に慌てて記事を書いています！間に合え！！

## やりたいこと
- 最近やっと `TypeScript` と `Vue.js` を使い始めたので `Vuex` も一緒に使ってみたい。
- とりあえず `vue-cli` でプロジェクトを作成すると、 `store.ts` ってファイルがぺろっと一つ追加される。けど、できれば1ファイルにべた書きするんじゃなくて、いいかんじに分けて書きたい。けど何をどう書けばいいのかよくわからない:thinking:
    - あとたぶん別の言語やって1ヶ月もしたら内容をすっぱり忘れるので、備忘録もほしい:innocent:

というわけで、調べてみた。

## 結論
ここにぜんぶ載ってた。すてき。
https://codeburst.io/vuex-and-typescript-3427ba78cfa8

……なのですが。これだけだと、ちょっと
弊社の偉い人に怒られてしｍ
記事としてアレなので、もうちょい噛み砕いた内容で書いていきます。

## 注意
2018/12/10 21:00 現在、Vue.jsの最新版にバグがあるようで、掲載した内容について動作確認まで行えていません（一応バグのある箇所以外はコンパイルエラーにならないことだけは確認してますが）。動作確認できてから改めてソースコードなど手直しを行います…
https://github.com/vuejs/vue/pull/9173

## 本題
### ファイル構成
`vue-cli create hoge` で作成されたテンプレから`HelloWorld.vue` をひっこぬいて `Todo.vue` と　`store` 以下を足したような状態です。（`src` 以下の関係あるもののみ抜粋）

```
src
├── App.vue
├── components
│   └── Todo.vue
├── main.ts
└── store
    ├── Todo
    │   ├── actions.ts
    │   ├── getters.ts
    │   ├── index.ts
    │   └── mutations.ts
    ├── index.ts
    └── types.ts

```
`src/store/Todo` が `Vuex` のモジュールで、 `src/store` がルートステートになります。
モジュールについては公式のドキュメントをどうぞ。 https://vuex.vuejs.org/ja/guide/modules.html

### TodoModule
TODOのリストを管理するやつをイメージしています。
実際はもっとたくさんあれこれ処理があるはずですが、サンプルなので以下省略。

#### types.ts
モジュール定義の前に、そちらで参照する `interface` の定義です。

```ts
export interface RootState {
  version: string;
}

export interface TodosState {
  todos: Todo[];
}

export interface Todo {
  // 一意になるかんじのID
  id: string;
  // チェックボックスON/OFF
  done: boolean;
  // やること
  text: string;
}
```

特になんの変哲もない `interface` ですね。
`RootState` と `TodosState` が今回の `state` の定義になります。

#### Module（index.ts）
```typescript:store/Todo/index.ts
import { Module } from 'vuex';
import { TodosState, RootState } from '@/store/types';
import getters from './getters';
import actions from './actions';
import mutations from './mutations';

const state: TodosState = {
  todos: [],
};

export const todos: Module<TodosState, RootState> = {
  namespaced: true,
  state,
  getters,
  actions,
  mutations,
};
```
`Module` のオブジェクトを用意しています。以上。
`getters`, `actions`, `mutations` の定義は続けて以下に記載します。`namespaced: true` にしておくと、このあとコンポーネントから参照するときにらくちんです。

#### Actions
```typescript:store/Todo/actions.ts
import { ActionTree } from 'vuex';
import { TodosState, RootState, Todo } from '@/store/types';

const actions: ActionTree<TodosState, RootState> = {
  add: async ({ commit }, todo: Todo) => {
    if (await someAsyncAddMethod(todo)) {
      commit('add', todo);
      // 成功
      return true;
    }
    // 失敗
    return false;
  },

  remove: async ({ commit }, id: string ) => {
    if (await someAsyncRemoveMethod(id)) {
      commit('remove', id);
      return true;
    }
    return false;
  },

};

export default actions;
```
`ActionTree` を実装したオブジェクトを用意します。それだけ。
総称型の指定は `ActionTree<モジュールのState, ルートのState>` です。
引数の `{ commit }` については、他にも定義がありますが、このへんは公式ドキュメントのままですのでそちらを参照してください。

`mutation` を直接呼び出すか、必ず `action` を介するかは好みでいいんじゃないかなーと思いますが、サーバのAPIを叩くなど非同期処理を伴う場合は必ず `action` 経由で行いましょう。`mutation` で非同期処理はダメ、絶対。

#### Getters
```typescript:store/Todo/getters.ts
import { GetterTree } from 'vuex';
import { TodosState, RootState } from '@/store/types';

const getters: GetterTree<TodosState, RootState> = {
  size: (state: TodosState) => {
    return state.todos.length;
  },
};

export default getters;
```

とりあえず感がすごい。よい例が思いつかずすみません。
ここでは`GetterTree`のオブジェクトを用意します。引数については `action` と同様です。

`state` の内容を加工して参照する必要があるものは`getter`を介するのかなーと思いますが、自分の場合、単純にそのまま参照して問題ないなら `state` の内容を直接参照しちゃっても問題ないんじゃないかな派です。

#### Mutations
```typescript:store/Todo/mutations.ts
import { MutationTree } from 'vuex';
import { TodosState, Todo } from '@/store/types';

const mutations: MutationTree<TodosState> = {
  add: (state, todo: Todo) => {
    state.todos.push(todo);
  },
  remove: (state, id: string) => {
    state.todos = state.todos.filter((e: Todo) => e.id !== id);
  },
};

export default mutations;
```
`state` を編集する役割を担うもの。これはあんまり迷わなくていいですね。
1つ気になってるのは `state.todos.push(todo);` の行。オブジェクトに対する編集になるのか、配列に対する編集扱いになるのかがまだちょっとはっきりしません。（このへんは動作確認したら追記します）


### RootState
ルートの定義はこんなかんじ

```typescript:store/index.ts
import Vuex, { StoreOptions } from 'vuex';
import { RootState } from './types';
import { todos } from './Todo';

const store: StoreOptions<RootState> = {
  state: {
    version: '1.0.0',
  },
  modules: {
    todos,
  },
};

export default new Vuex.Store<RootState>(store);
```

`import { todos } from './Todo';` で、上記でつくった `TodosState` をインポートしています。
さらにそこからルートステートの `modules` に設定しています。


### コンポーネントからの参照
```typescript:components/todo.vue
// @Component
export default class TodoComponent extends Vue {

  @TodoModule.State('todos')
  private todos!: Todo[];

  @TodoModule.Getter('size')
  private size!: number;

  @TodoModule.Action('add')
  private add!: (todo: Todo) => Promise<boolean>;

  private inputText: string = '';

  private addTodo(): void {
    // validate
    if (!this.inputText) {
      window.alert('なにか入力してね！');
      return;
    }
    this.add({
      id: new Date().getMilliseconds().toString(),
      // チェックボックスON/OFF
      done: false,
      // やること
      text: this.inputText,
    });
  }
}
```

コンポーネントなどから呼び出すときは、最初の記事にあったように `vue-class` を使うのが良さげです。
ただ、現在は最初の記事の内容から比べるといくらか書き方が変わっているようですので、詳細は公式のドキュメントを確認してください。
コードを全部書くと記事が長くなるのでだいぶ端折っていますが、基本的には`@Action`, `@Mutation`, `@Getter` のアノテーションでプロパティのようにぽんぽん定義していくだけ。モジュール側を参照するときは `namespace('moduleName')` を経由するだけ。簡単ですね！

## まとめ
最初の記事のおかげでTypeScriptでのVuexの使い方がなんとなくつかめたので、助かりました。
あと、迷ったらとりあえず [型定義ファイル](https://github.com/vuejs/vuex/blob/dev/types/index.d.ts) を見るというくせもつきました。以前と比べるとだいぶさくっとVue.jsアプリが作れるようになってきたので、あとは忘れないようにしないと…:joy: