Vue+TypeScript開発でvuex-module-decoratorsを使って優勝する

# はじめに
最近流行っててみんな大好きなTypeScriptですが、VueやReactと合わせて開発するケースが増えてきています。
だがしかし、TypeScriptとVuexの相性は悪く、vuexのstate, action, mutation, getterをコンポーネント内で使おうとしたときに型安全が守られない、インテリセンスが効かない等の状態になり、非常に使い辛い。
これらの解決策としてサードパーティ製のパッケージであるvuex-module-decoratorsを使って優勝する方法をサンプルアプリケーションを元に記述していきます。

# 構成

今回は[こちら](https://github.com/fyui001/vue-app-sample)のアプリケーションを元に書いていきます。

# storeを分割して登録する
createStoreで使いたいmoduleを登録します。
これで用途毎にVuexをmoduleで分割することが出来ます。(いい話

```store.ts
import Vuex from 'vuex'
import Vue from 'vue'
import { UserModuleClass, registerUserModule } from './UserModule'
import { PhotoModuleClass, registerPhotoModule } from './PhotoModule'

Vue.use(Vuex)

export interface RootState {
  UserModuleStore?: UserModuleClass,
  PhotoModuleStore?: PhotoModuleClass,
}

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export default function createStore() {
  const store = new Vuex.Store<RootState>({})
  // とりあえず必要なmoduleはここでregisterする
  registerUserModule(store)
  registerPhotoModule(store)
  return store
```

#  vuex-module-decoratorsを使って優勝する

画面からapiにリクエストを送る簡単なログイン認証とその状態管理を実装していきます。
store.tsの中身はこんな感じです。

vuex-module-decoratorsを使った場合ではstate, action, mutationの型を使うことが出来てます。


```User.ts
export default interface UserStateType {
  userId: string;
  name: string;
}
```

stateの型は`src/modules/User.ts` にあるUserStateTypeで型を定義しています。

```UserModule.ts
import { Mutation, Action, VuexModule, getModule, Module } from 'vuex-module-decorators'
import { Store } from 'vuex'
import { RootState } from './store'
import UserStateType from '../modules/User'
import ApiRequest, {SendCredential} from '../client/api'

@Module({name: 'UserModuleStore', namespaced: true, stateFactory: true})
export class UserModuleClass extends VuexModule {

  user: UserStateType = {
    userId: '',
    name: '',
  }

  isLogin: boolean = false

  @Mutation
  public SET_USER(param: UserStateType) {
    this.user = param
  }

  @Mutation
  public SET_IS_LOGIN(param: boolean) {
    this.isLogin = param
  }

  @Action
  public async loginAction(credential: SendCredential) {
    const result = await ApiRequest.postLoginRequest(credential)
    if (result.data !== undefined) {
      document.cookie = 'access_token=' + result.data.access_token + ';'
      this.SET_USER({
        userId: result.data.user_id,
        name: result.data.name,
      })
    }
  }

  @Action
  public async logoutAction() {
    let accessToken = ''
    const cookies = document.cookie
    const cookiesArray = cookies.split('; ')
    for (const c of cookiesArray) {
      const keyValue = c.split('=')
      if ( keyValue[0] === 'access_token') {
        accessToken = keyValue[1]
      }
    }
    await ApiRequest.postLogoutAction(accessToken)
  }

  @Action
  public async isLoginCheckAction() {

    let accessToken = ''
    const cookies = document.cookie
    const cookiesArray = cookies.split('; ')
    for (const c of cookiesArray) {
      const keyValue = c.split('=')
      if ( keyValue[0] === 'access_token') {
        accessToken = keyValue[1]
      }
    }
    await ApiRequest.bearerAuthentication(accessToken).then((result) => {
      this.SET_USER({
        userId: result.data.user_id,
        name: result.data.name
      })
      this.SET_IS_LOGIN(true)
    }).catch(() => {
      throw false
    })

  }

}

const UserVuexModule = (store?: Store<RootState>) => getModule(UserModuleClass, store)
export default UserVuexModule

export function registerUserModule(store: Store<RootState>) {
  if (!store.state.UserModuleStore) {
    store.registerModule('UserModuleStore', UserModuleClass)
  }
}

```
storeはvuexのstoreを示しており、このモジュールがどのstoreに属しているのかを`@Module`で明示的に示す必要あります。
moduleがclass化されたので、それぞれのメソッドの中でthisを使ってお互いを呼び出すことができる。
特に優れている点がactionsでのmutationの呼び出しです。
従来のvuexのactions内でのmutationやactionsの呼び方では

```mutation.ts
commit('SET_USER', 'any') //何でも受け付ける
```

のような書き方になり、型チェックが出来なかったのが

```UserModule.ts
this.SET_USER({
  userId: result.data.user_id,
  name: result.data.name
}) // UserStateType型でないとエラーになる
```

のように記述できるようになる。
mutation `SET_USER` は、引数にUserStateType型の値を要求しており、コンポーネントやactionsで呼び出したときも、vuexでの型チェックがしっかり行われている事がわかります。

# コンポーネントからの呼び出し方

使用したいvuexのモジュールクラスをimportする

```auth-nav.vue
<template>
  <div class="auth-nav">
    <b-navbar type="dark" variant="info" toggleable="lg">
      <b-button v-b-toggle.sidebar-backdrop variant="info">
        <b-icon icon="list"></b-icon>
      </b-button>
      <b-navbar-brand href="/">
        Vue.jsサンプル
      </b-navbar-brand>
      <Sidebar />
      <b-navbar-toggle target="guest-navigation"/>
        <b-collapse is-nav id="guest-navigation">
          <b-navbar-nav class="ml-auto">
            <b-nav-item to="/photos/upload/" right>写真を共有</b-nav-item>
              <b-nav-item-dropdown right>
                  <template v-slot:button-content>
                      <em>{{ userId }}</em>
                  </template>
                  <b-dropdown-item-button @click="handleLogout">
                    LOGOUT
                  </b-dropdown-item-button>
              </b-nav-item-dropdown>
          </b-navbar-nav>
        </b-collapse>
      </b-navbar>
  </div>
</template>

<script lang="ts">
  import Vue from 'vue'
  import Sidebar from '../sidevar/sidevar.vue'
  import UserVuexModule from '../../../store/UserModule' // モジュールクラスをimport
  export default Vue.extend({
    name: 'AuthNav',
    data() {
      return {
        userId: ''
      }
    },
    components: {
      Sidebar
    },
    async created() {
      this.userId = UserVuexModule(this.$store).user.userId // stateの取得
    },
    methods: {
      async handleLogout() {
        await UserVuexModule(this.$store).logoutAction() // Actionの実行
        window.location.href = '/'
      }
    }
  })
</script>

<style scoped>

</style>
```

# 最後に

Vuex + TypeScriptはまだまだ発展途上で型安全性とインテリセンスに困らされるケースが多いと思います。
今回はvuex-module-decoratorsを使ってこの問題を解決してみましたが、他の解決法があれば調査してみたい気持ちです。

# 参考文献
[Writing Vuex modules in neat Typescript classes](https://medium.com/coding-blocks/writing-vuex-modules-in-neat-typescript-classes-9bf7b505e7b5)

[vuex-module-decorators](https://github.com/championswimmer/vuex-module-decorators)