Flux的なVue実装方法

#前言
センシンロボティクスのサツキです。
前職はフルスタックエンジニアですが、SRには主にWebフロントエンド開発を担当します。

#現状
ドローンの無人運用を実現できるように、フライトプランの作成から実行まで対応する、Vueベースのシングルページアプリ(SPA)が開発していた。

下記は簡素化した構成です。

```
<base>
  <map/>
  <details/>
</base>
```

`map`と`details`間は`EventBus`に通じて状態の受け渡しを行います。

#課題

だたし、時間を経過すると、いくつの問題が浮きあがりました。

1. `<map/>`の初期化が時間がかかり、時により`<details/>`から発火する時点はリスナーがまだ立ち上げなかったので、反映できなかった。
2. `<map/>`と`<details/>`が両方フライトプランを変更する場合がありますので、アプリが大きくなるとデバッグが難しくなる。
3. その後Vue Routerのネストルートを導入し、`<overview/>`、`<edit-form/>`、`<flight-viewer/>`を分けた。これで状態の受け渡しが難しくなりました。

#一方通行

![Image](https://facebook.github.io/flux/img/overview/flux-simple-f8-diagram-explained-1300w.png)

その答えは、Fluxです。
簡単といえば
1. 共通状態をStoreに置く
2. StoreはSetterを持たず。Viewは`action`を通じて変更を促進するしかない。
3. Storeはdispatcherから渡した`action`に応じて変更して、変更が終わったらViewに通知する。
4. ViewはStore状態に応じて表示する。

きちんと設計したFluxアーキテクチャはデータフローが明瞭になり、状態変更の追従もユニットテストも楽になります。一方、Flux設計はハードルが高く、初心者にとっては難しいかもしれません。

#Vuex
![Image](https://vuex.vuejs.org/vuex.png)

Vuexも、そんなFlux実装の一つです。ただし、本家FluxやReduxなどと違い

1. VuexはVueに依存する。逆にいえば、VuexはVueとの相性が良く、整合もより簡単です。
2. Vuexは最初から`async`が対応する。
3. FluxとReduxは新しい`state`を返さなくて検知できない。それに対し、Vuexは`state`値を変更するだけで検知できる。

#例

store.js

```
import axios from 'axios';

const state = {
  currentId: undefined,
  allFlightPlans: [],
}

const actions = {
  index({commit, state}) {
    axios.index(url, (allFlightPlans) => {
      commit('receive', allFlightPlans)
      if (allFlightPlans.length > 0) {
        commit('setCurrent', allFlightPlans[0])
      }
    }
  },
  select({commit, state}, flightPlan) {
    commit('setCurrent', flightPlan);
  },
}

const mutations = {
  receive(state, allFlightPlans) {
    state.allFlightPlans = allFlightPlans;
  },
  setCurrent(state, flightPlan) {
    state.currentId = flightPlan.id;
  },
}

const getters = {
  currentIndex(state) {
    return state.allFlightPlans.findIndex(item => item.id === state.currentId) + 1;
  },
}

export default {
  state,
  actions,
  mutations,
  getters
}
```

viewer.vue

```
<template>
  <div>
    <div>表示中ルート： {{selectedIndex}}/{{allFlightPlans.length}}</div>
    <ol>
      <li :class="{active: item.id === currentFlightPlanId}
          v-for="item in allFlightPlans"
          @click="setCurrentFlightPlan(item)">
        {{ item.name }} (最終更新日時: {{item.updatedAt}})
      </li>
    </ol>
  </div>
</template>
<script type="text/javascript">
import { mapState, mapActions, mapGetters } from 'vuex';
export default {
  computed: {
    ...mapState({
      allFlightPlans: state => state.allFlightPlans,
      currentFlightPlanId: state => state.currentId
    }),
    ...mapGetters({
      selectedIndex: 'currentIndex',
   }),
  },
  methods: {
    ...mapActions([
      setCurrentFlightPlan: 'select',
      indexFlightPlans: 'index',
    }),
  },
  created() {
    this.indexFlightPlans();
  }
}
</script>
<style scoped lang="scss">
  /* 略 */
</style>
```

#最後に

残念しながら、実際の本番アプリは例より遥かに複雑で、リファクタリング作業は思ったより時間がかかってしまった。とはいえ、同僚によると、変更した部分はバグが減っているし、新規開発は今までよりスムーズになるようです。

> Flux ライブラリは眼鏡のようなものです: あなたが必要な時にいつでも分かるのです。
> -- Dan Abramov、Redux 開発者

そもそもFluxの実装は複雑ですが、うちの場合はメリットあると思います。

#参照
[Flux公式ガイド](https://facebook.github.io/flux/)
[Vuex公式ガイド](https://vuex.vuejs.org/ja/)
他、stackoverflowなど