Vue.js童貞がネコ本読んで得たもの⑤

## はじめに
本稿はフロントエンド領域から逃げてきたバックエンドエンジニアが追い詰められて0から勉強した軌跡である。詳細は前回をご参照ください＾＾

### ネコ本とは？

みんな大好きMio様の名著「基礎から学ぶ Vue.js」です。
https://www.amazon.co.jp/dp/B07D9BYHMZ

## 童貞卒業までの道程

- （済）基本
- （済）データバインディング、条件分岐、繰り返し
- （済）ハンドラ、双方向バインディング
- （済）算出プロパティ
- （済）ウォッチャ、フィルタ
- （済）コンポーネント
- （済）VueCLI、単一ファイルコンポーネント
- Vuex
- Vue Router ←今日はここまで
- ??? 

あともう少しで童貞卒業です＾＾

## Vuex

アプリケーション内で唯一無二なデータ管理用の箱とデータ関連の便利機能を提供してくれるプラグインですね。
コンポーネント間でいちいちデータのやり取りを繰り返していても埒が明かないので、データ管理を一元化しようって寸法ですね＾＾

### インストール

vuexのインストールをnpmで行う場合は以下を実行します。

```sh
  $ npm install vuex babel-polyfill
```

### ストアの登録

ストアとは、前述したデータ管理用の箱ですね。

Vuexはストアで定義したstateがアプリケーション全体で利用されるリアクティブデータで、そのデータのライフサイクルをアクションやミューテーションで構築するようなFluxライクな機能を提供してくれます。

ネコ本にならい、getterもお忘れなく。

まずは、以下の内容で`./src/store.js`を作成しましょう。

```js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store new Vuex.Store({
  // リアクティブなデータを定義する。
  state: {
    message : 'Hello Vue.js'
  },
  // stateで定義したリアクティブデータのアクセッサ。直接stateにもアクセスできるが混乱を防ぐために明示的に記述することが推奨されるケースもある。
  getters: {
    message(state) { return state.message }
  },
  // stateで定義されたリアクティブデータを更新する。主にactionからのみ呼び出される。
  mutations: {
    setMessage(state, payload) {
      state.message = payload.message
    }
  },
  // APIの呼び出しや入力チェックなどの前処理を担当する。コンポーネントや他のアクションから呼び出される。
  actions: {
    update({ commit }, message) {
      commit('setMessage', {message})
    }
  }
})

export default store
```

これで、ストアの作成ができました。
stateに`message`というリアクティブデータを定義し、適当にライフサイクルに必要な処理を記載していますが、詳しくはインラインのコメントをみてください＾＾

上記で定義したストアをアプリケーションに登録するには、`src/main.js`に以下のよう記述します。

```js
import Vue from 'vue'
import App from './App'
import store from './store.js'  // ここを追記

Vue.config.productionTip = false

new Vue({
  el: '#app',
  store,  // ここも追記
  components: { App },
  template: '<App/>'
})

```

これで、ストアがアプリケーションに登録されました。

## コンポーネントからストアを利用

せっかくストアを登録したので使いましょう。
どこから？コンポーネントからです＾＾

コンポーネントは、`src/components`配下に配置しましょう。
以下の内容を`src/components/EditForm.vue`に記載します。

```vue
<template>
<div class='edit-form'>
  <input type="text" v-bind:value="message" v-on:input="doUpdate"/>
</div>
</template>

<script>
export default {
  name: 'EditForm',
  computed: {
    message() {return this.$store.getters.message}
  },
  methods: {
    doUpdate(event) {
      this.$store.dispatch('update', event.target.value)
    }
  }
}
</script>
```

はい、これでコンポーネントから登録したストアを利用できましたね＾＾

### マッピングヘルパーで一括登録

ネコ本ではマッピングヘルパーと記載されていますが、正式名称は内容ですね。本稿ではネコ本に習ってそう呼称します。
複数のストアを呼び出すとなると、`this.$store.`を何度も記述しなければなりませんね。はい、面倒です。
マッピングヘルパーを使うと、その冗長な記述を省略することができます。便利なので、普段から使っていきましょう。


```vue
<template>
<div class='edit-form'>
  <input type="text" v-bind:value="message" v-on:input="doUpdate"/>
</div>
</template>

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

export default {
  name: 'EditForm',
  computed: {
    // message() {return this.$store.getters.message}
    ...mapGetters(['message'])
  },
  methods: {
    ...mapActions(['update']),
    doUpdate(event) {
      //this.$store.dispatch('update', event.target.value)
      this.update(event.target.value)
    }
  }
}
</script>
```

挙動は同じですが、マッピングヘルパーを使うと上記のように記述できます。

### ストアの分割

ちょっとアプリケーションが大きくなると扱うデータの数も多くなりますね。
それを全てVuexで管理しようとすると、ストアがあっという間に肥大化して管理しづらくなります。

モジュールを使って肥大化したストアを分割しましょう。
分割の粒度はデータモデル設計の腕の見せ所ですね＾＾

まずは試しに`moduleA`と`moduleB`というファイルを以下のように追加しましょう。

```
${APP_ROOT}
└── src
    ├── App.vue
    ├── assets
    │   └── logo.png
    ├── components
    │   ├── EditForm.vue
    │   └── HelloWorld.vue
    ├── main.js
    ├── store.js
    └── stores　←追加
        ├── module_a.js　←追加
        └── module_b.js　←追加
```

moduleA、moduleB、および呼び出し元の`store.js`の記述は以下の通りです。


```js
// ./src/stores/module_a.js

export default {
  namespaced: true,
  state: {
    message : 'Hello Vue.js(module_a)'
  },
  getters: {
    message(state) { return state.message }
  },
  mutations: {
    setMessage(state, payload) {
      state.message = payload.message
    }
  },
  actions: {
    update({ commit }, message) {
      commit('setMessage', {message})
    }
  }
}
```


```js
// ./src/stores/module_b.js

export default {
  namespaced: true,
  state: {
    message : 'Hello Vue.js(module_b)'
  },
  getters: {
    message(state) { return state.message }
  },
  mutations: {
    setMessage(state, payload) {
      state.message = payload.message
    }
  },
  actions: {
    update({ commit }, message) {
      commit('setMessage', {message})
    }
  }
}
```

```js
// ./src/store.js

import Vue from 'vue'
import Vuex from 'vuex'
import moduleA from '@/stores/module_a.js'
import moduleB from '@/stores/module_b.js'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    moduleA,
    moduleB
  }
})

export default store
```

呼び出し元のコンポーネントは、以下の通りですね。

```vue
<template>
<div class='edit-form'>
  <input type="text" v-bind:value="messageA" v-on:input="doUpdateA"/>
  <input type="text" v-bind:value="messageB" v-on:input="doUpdateB"/>
</div>
</template>

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

export default {
  name: 'EditForm',
  computed: {
    ...mapGetters({'messageA': 'moduleA/message'}),
    ...mapGetters({'messageB': 'moduleB/message'}),
  },
  methods: {
    ...mapActions({'updateA': 'moduleA/update'}),
    doUpdateA(event) {
      this.updateA(event.target.value)
    },
    ...mapActions({'updateB': 'moduleB/update'}),
    doUpdateB(event) {
      this.updateB(event.target.value)
    }
  }
}
</script>
```

これで、moduleAとmoduleBをそれぞれコンポーネントから呼び出すことができました＾＾

## Vue Router

Vue Routerとは、SPAを構築するためのVueの拡張ライブラリです。
SPAですよ！SPA！それっぽくなってきました＾＾


### インストール

Vue Routerのインストールをnpmで行う場合はvuexと同様に以下を実行します。

```sh
  $ npm install vue-router
```
### ルート定義の登録

ルート定義を`./src/router.js`というファイルを追加して記載しましょう。

```js
// ./src/router.js

import Vue from 'vue'
import VueRouter from 'vue-router'

import Home from '@/pages/Home.vue'
import Product from '@/pages/Product.vue'

Vue.use(VueRouter)

const router = new VueRouter({
  routes: [
    {name: 'home', path: '/', component: Home},
    {name: 'product', path: '/product/:id', component: Product, props: route => ({id: Number(route.params.id)})},
  ]
})

export default router
```

上記で作成したルート定義をアプリケーションに登録します。

```js
// ./src/main.js

import Vue from 'vue'
import App from './App'
import router from './router.js'

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

```


ここで追加した２ページ（「/」と「/product/:id」）に対応するコンポーネントを作成しましょう。

```vue
// ./src/pages/Home.vue

<template>
  <div class="home">
    <h1>Home</h1>
  </div>
</template>

<script>
  export default {
    name: 'Home'
  }
</script>
```

```vue
// ./src/pages/Product.vue

<template>
  <div class="product">
    <h1>Product</h1>
    <p>id : {{id}}</p>
  </div>
</template>

<script>
export default {
  name: 'Product',
  props: {id: Number}

}
</script>
```

これで、簡単なSPAを構築できました＾＾

## 終わりに

ネコ本を一通り舐め回しましたが、これで脱童貞できた気がしない。。。
あと何が必要なんだろう？？？会社のフロントエンドエンジニアに聞いてみます＾＾