Vuexを用いた中規模以上WebサービスへのVue.jsアーキテクチャリングの導入


この記事は CBcloud Advent Calendar 2019 11日目の記事です。

今回は業務で導入したVue.jsとVuexのアーキテクチャについてまとめました。
なおこちらの内容は Vue.jsアーキテクチャリング勉強会
にて登壇したものから一部抜粋しまとめたものです。

1. Vue.js とは?

他フレームワークと比較すると一番特徴的なのは、下記の一行だけでVue.jsの機能が利用できることにあると思います。

 <script src="https://cdn.jsdelivr.net/npm/vue"></script>

2. Vuexとは?

公式サイトにはvuexについてこのように書かれています。

Vue.js アプリケーションのための 状態管理パターン + ライブラリです。

公式サイトで書かれているVuexを表す図は有名だと思いますが、個人的にはより正確に表すなら下記の図が適していると考えております。

ここで抑えておきたいのは、下記の2点です。

  • 参照処理はGettersに寄せる
  • 更新処理はActionsに寄せる

なぜGettersとActionsに処理を寄せるのか?

なぜGettersとActionsに処理を寄せるのか?を見る前にVuexの状態変化はそれぞれ下記の役割があります。

  • State
    • データの保管先
  • Mutations
    • データの変更
  • Getters
    • データの取得
  • Actions
    • データの更新処理

それぞれ役割を把握した上で、私がGettersとActionsに処理を寄せる理由は下記の2点が大きいと考えています。

  • Stateを直接参照すると、Vuexに保存されているデータを何も介さずに見ることになる。Getterなどで表示内容を変更して参照している場合があるため、情報の一意性が保てなくなるため

  • Mutationを呼ぶことでvueファイルから、Stateの内容を直接書き換えることができる。これはロジックをvueファイルに内在してしまうので、更新処理はActionsに寄せる必要がある。

Actions



export interface RootState {
    version: string;
}

export interface TeamsState {
    teams: Team[]
}

export interface Team {
    id: number;
    name:string;
    area: Area;
   crestUrl: string;
}

export interface Area {
    id: string;
    name: string;
}

Getters

export const getters = {
  fullName: state => {
    return state.firstName + state.lastName
  },
  array: state => {
    return state.array
  }
}

State

export const state = () => ({
  array: [],
  firstName: ``, 
  lastName: ``,
})

Mutations

export const mutations = {
  [types.SET_ARRAY](state, array) {
    state.array = array 
  },
  [types.SET_FIRST_NAME](state, firstName) {
    state.firstName = firstName
  },
  [types.SET_LAST_NAME](state, lastName) {
    state.lastName = lastName
  }
}

Vueファイル

これらVuexで表されたものをvueファイル側で以下の様に呼び出します。
この規模のコード量であれは冗長的になってしまう様に見えますが、表示に必要な記述とロジックを分離することができています。

<template>
<components
:data=”data”
></components >
</template>

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

computed: {
...mapGetters(`Store`, [`data`])
},
async fetch({ store, params }) {
 store.dispatch(`Store/getFirstData`)
},
methods: {
   ...mapActions(`Store`, [`update`]),
   async update() {
     await this.update()
   }
}
</script>

3. より安全に開発する 〜TypeScriptの導入〜

Vue.jsではその名の通り、基本的にはJavaScriptが利用されるフレームワークです。
JavaScriptは動的型付けを採用しており、中規模〜大規模なWebサービスになった場合、
対象のデータがどんなものなのかわからず苦慮することがあります。

JavaScript のデータ型とデータ構造

そこでTypeScriptによって静的型付けを行うことで、この負担を軽減します。
実際にTypeScriptの導入を行うことで、IDE内でデータの予測変換が効く様になり、対象のデータがどんなものなのかの想定が楽になります。

TypeScriptの導入

TypeScriptではtypesとい型の定義をしたファイルを用意し、Vuexでそれらを利用することができます。

Actions

export interface RootState {
    version: string;
}

export interface TeamsState {
    teams: Team[]
}

export interface Team {
    id: number;
    name:string;
    area: Area;
   crestUrl: string;
}

export interface Area {
    id: string;
    name: string;
}

Actions

const actions: ActionTree<TeamsState, RootState>= {
  fetch: async ({ commit }) => {
    try {
      const res = await axios.get('https://api.football-data.org/v2/competitions/PL/teams/', {
        headers: {
          'X-Auth-Token': 'XXXX'
        }
      })
      const teams: Team[] = res.data.teams
      commit('teamsLoaded', teams)


    } catch (error) {
     // commit('teamsError')
    }  
  },
};

Getters

const getters: GetterTree<TeamsState, RootState> = {
  teams: (state: TeamsState) => {
    return state
  }
}

State

const state: TeamsState = {
  teams: [],
};

export const todos: Module<TeamsState, RootState> = {
  namespaced: true,
  state,
  getters,
  actions,
  mutations
}

Mutations


export const mutations: MutationTree<TeamsState> = {
    teamsLoaded(state, teams: Team[]) {
        state.teams = teams
    },
    teamsError(state) {
        state.teams = []
    }
}

こちらは実際にVuexをTypeScriptで記載したものになります。ご参考にしていただければ幸いです。
GitHub

4.まとめ

  • Vue.jsは他フレームワークと比較すると容易に導入可能
  • VuexはVue.js アプリケーションのための 状態管理パターン + ライブラリ で下記の様に使うべし
    • 参照処理はGettersによせる
    • 更新処理はActionsに寄せる
  • JavaScriptで動的型付けは心理的安全性の高い開発を…

以上ここまで読んでいただきありがとうございました!
私がまとめた中で間違いやこんな内容をもう少し書いてもらいたいなどありましたらお気軽にコメントお願いします!