LoginSignup
0

More than 1 year has passed since last update.

前言

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

現状

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

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

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

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

課題

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

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

一方通行

Image

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

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

Vuex

Image

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公式ガイド
Vuex公式ガイド
他、stackoverflowなど

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0