Vue + VuexでREST APIを叩いてリスト表示する方法

## 1. はじめに

だいぶ前に[React + ReduxでREST APIを叩いてリスト表示する方法](https://qiita.com/kazmaw/items/a2def8978127ffb11f92)という記事を書いたのですが、実際自分はVueをよく使うので、Vueでも同じことできるよっていうのを書いておこうと思います。

### 1-2. 作りたい機能概要

作るのは，[React + ReduxでREST APIを叩いてリスト表示する方法](https://qiita.com/kazmaw/items/a2def8978127ffb11f92)でやったのと基本的には同じで、ユーザの投稿（Post）の一覧を表示する機能です。
サーバー側はGETリクエストすると、

```js
[
  { id: '1', body: '朝ごはん食べた' },
  { id: '2', body: '昼ごはん食べた' },
  { id: '3', body: '夜ごはん食べた' },
]
```

という投稿の一覧を返します。それをリスト表示できればOKという感じです。

## 2. Vuexとは？

Vuex公式のドキュメントには、以下のようにあります。

> Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。
 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。 

Vueは、コンポーネントが一つであれば非常にシンプルなのですが、**共通の状態を共有する複数のコンポーネントを持ったときに**急に複雑になってしまいます。
プロパティ(props)が増えたり、それを孫でも使う、とかになると急に管理が面倒になりますし、親子コンポーネント間のデータの参照とかも必要になってくると、もう大変です。

それを解決するのが、Vuexです。
Vuexでは、コンポーネントから共有している状態を抽出し、それをグローバルシングルトンで管理します。
つまり、どのコンポーネントも状態(data)にアクセスしたり、アクション(methods)をトリガーしたりできるようになります。

イメージで言うと、手渡しでメモを回しながら管理すると、人が増えたりすると大変なので、ホワイトボードに張り出して管理しよう、って感じでしょうか。

この記事では実際にユーザの投稿をリスト表示するサンプルを作ってどんなものか理解したいと思います。

## 3. Vueプロジェクト生成

なにはともあれ、Vueのプロジェクトを作成してVuexを入れます。
プロジェクト作成は、vue-cliを使用すると簡単なので、それを使います。

```bash
vue create vuex-sample
```

Vuexは、yarnで。

```bash
yarn add vuex
```

## 4. APIの準備
APIの準備は今回の記事では直接関係ないので、[axiosでGETリクエストのモックを作成する
](https://qiita.com/kazmaw/items/45e0269f4438f88e1f67)に切り出しておきます。
練習がてら同じ環境でやりたい方はこちらを参考にしてください。

## 5. ストアの実装

`store/index.js`を作成して、そこにStoreを以下の定義します。

```js:store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    posts: [
      { id: 1, body: 'test1' },
      { id: 2, body: 'test2' },
      { id: 3, body: 'test3' }
    ]
  }
});
```

本当はAPIを叩いてデータ取得したいですが、まずはStoreの定義が出来ているかを確認するために、stateのpostsにダミーでデータを仕込んでいます。

そして、`main.js`でstoreを読み込みます。

```js:main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store';

Vue.config.productionTip = false

new Vue({
  store,
  render: h => h(App),
}).$mount('#app')
```

こうすることで、以下のようにして`store`をVueコンポーネントで使用することができるようになります。


```vuejs:Post.vue
<template>
  <div class="post">
    <ul>
      <li v-for="post in posts" :key="post.id">{{ post.body }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'Post',
  computed: {
    posts () {
      return this.$store.state.posts
    },
  }
}
</script>
```

複数のステートを扱いはじめると、全部に対して算出プロパティを宣言すると冗長になるので、mapStateヘルパーが用意されています。
以下のようにすると、postsというプロパティを使用することが出来ます。

```js:Post.vue
<script>
import { mapState } from 'vuex';

export default {
  name: 'Post',
  computed: {
    ...mapState(['posts'])
  }
}
</script>
```

ここまでで、一旦、サーバを起動して見ると、`posts`が取得できていることがわかるかと思います。

## 6. ミューテーションの実装

ミューテーションは、イベントみたいなもので、それをコミットすることでストアの状態を変更できます。

まず、Fluxでは、ミューテーションのタイプに定数を使用することが多いので、タイプを切り出して定義しておきます。

```js:store/mutation-types.js
const MUTATION_TYPES = {
  GET_POSTS_REQUEST: 'GET_POSTS_REQUEST',
  GET_POSTS_SUCCESS: 'GET_POSTS_SUCCESS',
  GET_POSTS_FAILURE: 'GET_POSTS_FAILURE'
}
export default MUTATION_TYPES
```

次に、ミューテーションを実装します。
ここでは、リクエスト開始、成功、失敗というミューテーションを実装します。
↓のように、ES2015の算出プロパティ名を使用して、[定数]とすることで、定数を関数名として使用できます。
（定数にしなくても、普通に`getPostsRequest(state) {....}`みたいな形でも定義できます。

```js:store/mutations.js
import MUTATION_TYPES from './mutation-types'

export const mutations = {
  [MUTATION_TYPES.GET_POSTS_REQUEST] (state) {
    state.isFetching = true
  },
  [MUTATION_TYPES.GET_POSTS_SUCCESS] (state, posts) {
    state.isFetching = false
    state.posts = posts
  },
  [MUTATION_TYPES.GET_POSTS_FAILURE] (state, err) {
    state.isFetching = false
    state.posts = null
    state.error = err
  }
}
```

ミューテーションがコミットされたときの処理を記述しています。
`GET_POSTS_REQUEST`でFetch中かどうかのフラグを変更し、`GET_POSTS_SUCCESS`で成功時にpostsにデータをセットし、`GET_POSTS_FAILURE`でエラー時の処理を実装しています。

## 7. アクションの実装

次にアクションの実装です。
アクションは、

- ミューテーションをコミット
- 非同期処理を組み込める

ので、APIはここで叩きます。

```js:store/actions.js
import Api from '../apis/api'
import MutationTypes from './mutation-types'

export default {
  getPosts ({ commit }) {
    commit(MutationTypes.GET_POSTS_REQUEST)
    return Api.getPosts()
      .then(res => commit(MutationTypes.GET_POSTS_SUCCESS, res.data))
      .catch(err => commit(MutationTypes.GET_POSTS_FAILURE, err))
  }
}
```

リクエストの開始と、終了(成功と失敗)でミューテーションをコミットして状態を変更してます。

## 8. コンポーネントへの導入

コンポーネントへ導入する前に、ミューテーションとアクションを`store/index.js`でセットしてあげないといけません。

```js:store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import { mutations } from './mutations'
import actions from './actions'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    isFetching: false,
    posts: [],
    error: null
  },
  mutations,
  actions
});
```

そして、`components/Post.vue`で以下のようにして使用します。

```js:components/Post.vue
<template>
  <div class="post">
    <ul>
      <li v-for="post in posts" :key="post.id">{{ post.body }}</li>
    </ul>
  </div>
</template>

<script>
import { mapState, mapActions } from 'vuex'

export default {
  name: 'Post',
  computed: {
    ...mapState(['posts'])
  },
  mounted () {
    this.$store.dispatch('getPosts')
  },
  methods: {
    ...mapActions (['getPosts'])
  }
}
</script>
```

算出プロパティで状態を監視し、メソッドでアクションを読み込んで、必要なタイミングでディスパッチしてあげると状態が更新されます。

ここまでやると、↓のような感じで表示できるようになったかと思います。
<img width="846" alt="スクリーンショット 2019-10-20 12.02.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56920/2fbf35ae-92e3-c106-131e-6b4530e4c30e.png">

## 9. まとめ

これで簡単なVuexのアプリを実装できるようになりました。[ソースコード](https://github.com/kazmaw/vuex-sample)も共有します。

これを機に、　[Vuex公式ドキュメント](https://vuex.vuejs.org/ja/)を結構読み込んだんですが、Reduxのときと比べて楽でした。なんせ、日本語で書いてあるので。実装自体もReduxより個人的にはシンプルな気がしました。

## 参考

- [Vuex公式ドキュメント](https://vuex.vuejs.org/ja/)
- [vue-cli + axios でmock環境を雑に作ったのでメモ](https://qiita.com/qazx7412/items/b52b5e818c226f1b5a21)