rails + Vue3(TypeScript) + Vuex4 + axiosでAPI出力【備忘録】

# はじめに
Vue3とVuex4にアップグレードされたのでその学習の備忘録である。
2年前くらいにVueをちょこっと触ってて、今現在Vue3.xになっており、どんな感じで変わったのか学習がてらやってみたらだいぶ書き方が変わっていて時間かかった。
Vue2.xのときはTypeScriptで書く為、`vue-property-decorator`などのライブラリを用いてクラスベースで書いていたが3.xになってからはそういったライブラリを使う必要がなくなった。
**※バックエンドのAPI出力は実装済みの記事である。**
## 開発環境
```
vue : 3.1.2
vuex : 4.0.2
ruby : 3.0.1
rails : 6.1.3
(今回はVue,Vuexの内容のため、あまりrailsのバージョンは関係ない)
```
# Vue3+Vuex4でのフロント側出力
色々な記事みるより公式を参考にするのが一番の近道！
[公式(TypeScript対応)を参考](https://next.vuex.vuejs.org/ja/guide/typescript-support.html)

## ファイル構成
**railsで作成しているため、ディレクトリは各々任意で！**

```
app/
└ javascript/
  ├ pack/
  │  ├ components/
  │  └ store/
  └ types/
```
今回関係ないフォルダは記載してません。
## Vuexの管理用ファイルを作成
型定義のkeyはべつになくてもできるっぽいけど、今回は公式に沿って`InjectionKey`を使ってちゃんと型定義します。

```app/javascript/packs/store/store.ts
import { InjectionKey } from 'vue'
import { createStore, useStore as baseUseStore, Store } from 'vuex'
import axios from 'axios';

//stateに対する変数の型付け定義
export interface State {
  articles: Array<any>
}

//型付け定義をkeyとして渡すための定義
//ちゃんと型付けてkeyとして渡さないと、Vuexの変数を使う時、型がわからんって怒られる。
export const key: InjectionKey<Store<State>> = Symbol()

export const store = createStore<State>({
  //↑で定義したstateをセットする。
  state: {
    articles: []
  },
  // mutationsは同期処理
  mutations: {
    // ③stateと渡されたresponse.dataを引数に含んでいる。
    setArticles(state, resData)
    {
      //④actionで取得したレスポンスデータをStateの変数に代入
      state.articles = resData
    }
  },
  // ①componentのdispatchメソッドで発火する。
  // actionsは非同期処理
  actions: {
    async fetchArticles(context) {
      await axios.get("/api/v1/articles")
      .then((response) => {
        // ②mutationsのsetArticlesメソッドへ
        context.commit("setArticles", response.data)
      })
      .catch(() => {
        alert("エラー");
      });
    }
  },
})

// componentファイルで毎度useStoreとkeyを読み込まないといけないところを
// useStoreにkeyものっけることでuseStoreを読み込むだけでよくなる。
// componentファイル実装のときにも説明あり。
export function useStore () {
  return baseUseStore(key)
}
```
## $storeでstateの値を呼び出せるようにする
これは任意。
componentファイルの`<templete>`で直接$storeを使うことによってVuexで管理しているStateの値を呼び出すことができるようになる。
今回だと`articles`なので`$store.state.articles`で値がとれるようになる。

```app/javascript/types/vuex.d.ts
// vuex.d.ts
import { ComponentCustomProperties } from 'vue'
import { Store } from 'vuex'

declare module '@vue/runtime-core' {
  // ストアのステートを宣言する
  interface State {
    articles: Array<any>
  }

  // `this.$store` の型付けを提供する
  interface ComponentCustomProperties {
    $store: Store<State>
  }
}
```

##  Vue App インスタンスに定義
公式の`main.ts`の部分

```app/javascript/packs/app.ts
import { createApp } from 'vue'
import App from './components/home_container.vue'
// .ts拡張子はいらない。
import { store, key } from './store/store'

document.addEventListener('DOMContentLoaded', () => {
  const app = createApp(App)
  // ここではstoreとkeyを渡す
  app.use(store, key)
  app.mount('#app')
})
```
## component実装
```app/javascript/packs/components/home_container.vue
<template>
  <div class="container">
    <div>{{ $store.state.articles }}</div>
    <div>{{ articles }}</div>
  </div>
</template>

<script lang="ts">
import { defineComponent, onMounted, computed } from "vue";
import { useStore } from '../store/store'

export default defineComponent({
  setup () {
    const store = useStore()
    // Vuexのmutationでarticlesに入れた値をここのarticlesに代入
    // なお、$storeで読み込めるようにしておけばこれをしなくても
    // $store.state.articlesで直接読み込める。
    const articles = computed(() => store.state.articles)
    onMounted(async () => {
      // VuexのactionのfetchArticlesメソッドを呼び出す。
      await store.dispatch("fetchArticles")
    })
    return {
      articles
    };
  }
})
</script>
```
Vuex管理ファイルの一番下で↓の処理していたが、

```
export function useStore () {
  return baseUseStore(key)
}
```
これがあるときは、keyをいちいち読み込まなくてもいい。

```
import { useStore } from '../store/store'
```
なければ毎回をkeyも読み込む必要がある。

```
import { useStore } from 'vuex'
import { key } from '../store/store'
```

## 処理の順番
①componentファイルのマウント内の処理

```vue
onMounted(async () => {
  await store.dispatch("fetchArticles")
})
```
②`dispatch`メソッドによってVuex管理ファイルの`action`の`fetchArticles`メソッドへ

```
actions: {
  async fetchArticles(context) {
    await axios.get("/api/v1/articles")
    .then((response) => {
      context.commit("setArticles", response.data)
    })
    .catch(() => {
      alert("エラー");
    });
  }
},
```
③ `commit`メソッドにより、`mutations`の`setArticles`メソッドへ

```
mutations: {
  setArticles(state, resData)
  {
    state.articles = resData
  }
},
```
これでVuexのStateの変数に値が代入されたので、あとは$storeで表示するか、componentの変数に代入して使うかどちらでもOK！

# おわり
今回はVue3の書き方からさらっていったので、割と時間がかかった。
こうやって書きたいけどVue3ではどう書くんだ？って。
バージョン１つ上がるだけでこんなに違うのかっていう。。。
でもライブラリなしでTypeScript使えるのは大きいと思う。
それがしたかったんだろうな〜。