LoginSignup
1
0

More than 3 years have 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など

1
0
0

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
  3. You can use dark theme
What you can do with signing up
1
0