Nuxt.js(+Vuex)にTypeScriptを段階的に導入する

とあるNuxt.jsプロジェクトにTypeScriptを段階的に導入しているので、その流れなどをここに書いておく。

サンプルとして簡単なTODOアプリをGitHubに置いてある。
https://github.com/hareku/nuxtjs-typescript-example

## TypeScriptの導入
https://ja.nuxtjs.org/guide/typescript/

公式ドキュメントにも書いてあるがTypeScriptの導入は割と簡単にできる。

1. `yarn add -D @nuxt/typescript ts-node`
2. `tsconfig.json`という空ファイルをプロジェクトのルートディレクトリに作成
3. `yarn run dev`すると、自動で`tsconfig.json`の中身を書いてくれる

`@nuxt/typescript`はNuxt.js用の`tsconfig.json`を生成するもので、`ts-node`はTypeScriptのファイルをトランスパイルせずに直接実行するためのもの。ESLintを使ってる人は他にもやることあるので、上の公式ドキュメントを見て欲しい。

### 型定義ファイルの追加
`tsconfig.json`で以下のように`@nuxt/vue-app`を読み込んでいるが、これはNuxt.jsが公式で用意しているNuxt関連の型定義ファイルだ。
https://github.com/nuxt/nuxt.js/tree/v2.8.0/packages/vue-app/types

```tsconfig.json
{
  "compilerOptions": {
    "types": [
      "@types/node",
      "@nuxt/vue-app"
    ]
  }
}
```

ここでasyncDataの型などを定義しているのだが、vuex周りやaxiosモジュールなどを追加したい場合はこちらを使えない。そのため上記のtypesディレクトリを、nuxtというディレクトリ名に変えてプロジェクトのルートディレクトリにコピペする。

そして`@nuxt/vue-app`から`./types/nuxt`に変えておく。

### Axiosモジュールの追加
Nuxt.jsではAxiosモジュールを導入することが多い。そのAxiosの型推論を効かせたい場合は、先ほど追加した`types/nuxt/index.d.ts`のContextに以下を追加する。

```index.d.ts
import { NuxtAxiosInstance } from "@nuxtjs/axios"

export interface Context {
  // ...
  $axios: NuxtAxiosInstance
}
```

これでasyncDataなどの引数から$axiosを型推論付きで呼び出すことができる。

## デコレータは使わない
Vue.jsでは[kaorun343/vue-property-decorator](https://github.com/kaorun343/vue-property-decorator)というクラスとデコレータを使った型推論をする人が多かった。しかしデコレータの理解や、Vue公式ドキュメントに書かれている従来の書き方から離れているため、少し高いハードルがある。
このクラス方式も公式がVue3.0から用意しようとしていたが、取り消しになったようだ。
[[Abandoned] Class API proposal by yyx990803 · Pull Request #17 · vuejs/rfcs · GitHub](https://github.com/vuejs/rfcs/pull/17#issuecomment-494242121)

そのため、vue-property-decoratorを使わず、普通に`Vue.Extend`を使ってTypeScriptを扱うのが現状ベストなような気がする。Vue3.0への移行も簡単になるし、何より書き方をTypeScript導入前とほとんど変えずにできるのが良い。

ちなみに`Vue.Extend`の場合はMixinにあるメソッドなどの推論が効かない。これに関してはVue3.0に期待だが…、今はMixinは使わないという方針にした。Mixinはな、あれは麻薬じゃ。書いている時は気持ち良いかも知れんが、可読性が著しく下がる。自分もVuexのMapを駆使したMixinを作っていたが、今は反省している。Mixinは辞めよう。

## Vuexの型推論
Vuexが公式で用意している型ファイルでは型推論が十分ではない。そのため独自の型定義ファイルを用意する。

Vuexの型定義ファイルに関してはこちらのリポジトリを参考にした。
https://github.com/takefumi-yoshii/ts-nuxtjs-express

これによって以下のようにdispatchやcommit関数で渡す引数をすべて推論することができる。

![vuex-typescript.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109606/8ed501a8-5b7c-411a-b5b7-2a68bebfe0de.png)

各Storeモジュールごとに以下のように型定義ファイルを書く必要がある。

```store/todo/type.ts
import { TODO } from '~/types/todo'

export interface S {
  list: TODO[]
}

export interface G {}
export interface RG {}

export interface M {
  setList: { todos: TODO[] }
}
export interface RM {
  'todos/setList': M['setList']
}

export interface A {
  fetchList: { limit: number }
}
export interface RA {
  'todos/fetchList': A['fetchList']
}
```

`this.$store`は[vuex側ですでに定義されてしまっている](https://github.com/vuejs/vuex/blob/v3.1.1/types/vue.d.ts)ため、プラグインとして他の変数でVuexを参照できるようにしている。
このやり方は[Nuxtでデコレーターを使わずTypeScriptを書いてみた \| MixDesign](https://nanaki.design/javascript/nuxt-typescript/)を参考にした。

```plugins/ex-store.ts
export default (context, inject) => {
  const store = new ExStore(context.store)

  inject('state', store.state())
  inject('getters', store.getters())
  inject('commit', store.commit())
  inject('dispatch', store.dispatch())
}

// 各componentからはthis.$stateやthis.$dispatchで呼び出せる
```

ちなみにTypeScriptをvuexに導入する際に、すべてのモジュールを書き換える必要はない。推論を効かせたいモジュールのみTypeScriptで書いて、他のJSのモジュールはそのままthis.$storeから呼び出せるので共存できる。

## まとめ
- TypeScriptはVuexにも段階的に導入できるよ
- vue-property-decoratorを使わなくても十分TypeScriptは運用できるよ
- Mixinは使わない方針に


冒頭でも紹介したが、簡単なサンプルとしてTODOアプリをGitHubに置いてあるので参考に。
https://github.com/hareku/nuxtjs-typescript-example