Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

新規プロジェクトにVueとVuexを採用してみた

こんにちは。Studyplus開発部の田口です。
新規で立ち上がった開発プロジェクトにVueVuexを採用してみたので、今回はその所感を書こうと思います。

Vue/Vuexの採用理由

今回のプロジェクトで開発するのは、Studyplusのアプリ内ブラウザのWebViewページです。
プロジェクトを開始するにあたって、アプリケーション自体は小規模で、画面の遷移がありつつサーバーとの通信を極力避けユーザーの入力情報を保持したいため、SPAで開発することにしました。
開発にあたって、

  • モダンな技術を採用し、社内に知見を貯めたい
  • フロントエンドの専任がチームにいないため、専任でないエンジニアがコードを読むときなるべくコストがかからない
  • (上記に関連して)日本語のドキュメントがあるとよい
  • メインで開発を行う自分が過去に少しだけFluxアーキテクチャを触ったことがある

という理由でVueとVuexの採用を決めました。
以下は日本語の公式サイトです

Vue: https://jp.vuejs.org/index.html
Vuex: https://vuex.vuejs.org/ja/

プロジェクトの立ち上げ

Vueの魅力のひとつに、アプリケーションの開発に役立つビルドやルーティングなどの機能が公式のサポートライブラリとして配布されており、エコシステムが充実していることが挙げられます。
今回利用した状態管理のVuexも、ルーティングで利用したvue-routerも公式が出しているライブラリです。基本的に公式が出しているライブラリが充実しているため、ライブラリの選定に悩まないところは採用して良かったと思った点です。
そして、プロジェクトの雛形を作成するCLIツールでああるVue CLIも、公式が出しているライブラリです。

Vue CLIを利用する上での注意点

Vue CLIはv3がリリースされて名称が変わっています。npm installする際に注意が必要です。

$npm install -g @vue/cli
または
$yarn global add @vue/cli

でインストールします。
パッケージ名の前半の@vueは、npmでライブラリを公開する際の名前空間です。この名前空間を利用することで、vue公式が出しているパッケージであることがすぐに分かったり、ライブラリ名が他のライブラリと被ってしまうことがなくなります。

@vue/cliをインストールする際に、Vue CLI v2以前のvue-cliがすでにインストールされている場合は、古いバージョンのアンインストールが必要です。また、Vue CLI v3のインストールにはNode.jsのv8.9以上のバージョンが必要です。

@vue/cliでプロジェクトの雛形を作成する

プロジェクトの作成はvue create project-nameでできます。すでにプロジェクトディレクトリを作成していた場合は、そのプロジェクトでvue create .を実行します。
コマンドライン上で自分が利用するパッケージを選択していくだけで、そのパッケージが入ったプロジェクトの雛形が作成されます。非常に便利です。

Webpackの設定を追加できる

Vue CLI v3で作成してプロジェクトは、v2とは違ってWebpackの設定が隠蔽されています。
ですが、自分でWebpackの設定を追加したい場合は、vue.config.jsというファイルをルートディレクトリに作成し、そこに追加の設定を書くことでWebpackの設定を独自にカスタマイズできます。 今回のプロジェクトでは独自設定はほとんどしませんでした。詳しくはリファレンスを参照してみてください。

Vuexを利用した状態管理

Vuexを使用した理由

今回はアプリケーションの状態管理をVuexで行うことにしました。
上述の通り、フロントエンド専任のエンジニアがいなかったことと、メインの開発を担当する自分がFluxアーキテクチャを触った経験があったということもありVuexを採用しました。
Nuxtを利用することも考えましたが、小規模なプロジェクトなこともあり今回はVuexのみ扱うことにしました。

Vuexの基本的な考え方

スクリーンショット 2019-01-24 22.34.12.png (30.2 kB)

Flux、 Redux そして The Elm Architectureから影響を受けています。

(https://vuex.vuejs.org/ja/ より引用)

とあるように、Vuexは単一方向のデータの流れが特徴のライブラリです。
基本的な考え方はFluxアーキテクチャと同じです。

Vuexでは、stateの更新をactionではなくmutationで行います。stateの更新の唯一の方法はmutationをcommitすることで、actionがdispatchするのはmutationということになります。ここがRedux等と違うところでしょうか。

actionの使い所は、非同期処理を行いその結果をstoreに反映させたいといった場面です。
React/Reduxを使う場合は、redux-sagaやredux-thunkなどのmiddlewareを導入すること考慮に入れる必要がありますが、Vuexではコアな機能としてデフォルトで利用できるので、ライブラリの選定コストがかからないところがいいですね。

今回は初めてのVuexの利用ということで、基本的に全てのstateの変更はactionを通すようにし、mutationの命名を定数で管理するようにしました。以下のような感じです。

// src/mutation_type.js

export const hogeTypes = {
  // hogeのリクエストが成功する
REQUEST_SUCCESS: "REQUEST_SUCCESS",

// hogeのリクエストが失敗する
REQUEST_FAILURE: "REQUEST_FAILURE"
}
...
// src/store/modules/hoge.js

import { hogeTypes } from "../../mutation_type"

const hogeModule = {
  namespaced: true,
  ...
  mutations: {
    [hogeTypes.REQUEST_SUCCESS](state, payload) {
      // 成功時のstateへの処理
    },
    [hogeTypes.REQUEST_FAILURE](state, payload) {
      // 失敗時のstateへの処理
    }
  },
  actions: {
    async requestHoge({ dispatch }) {
      // リクエスト処理
      try {
        const hoge = await fetchHoge()
        dispatch("doneFetchHoge", hoge)
      } catch (e) {
        dispatch("failureFetchHoge", e)
      }
    },
    doneFetchHoge({ commit }, hoge) {
      // リクエスト成功
      commit("REQUEST_SUCCESS", hoge)
    },
    failureFetchHoge({ commit }, e) {
      commit("REQUEST_FAILRE", e)
    }
  }
}

必ずしも全てのmutationをactionを通じてcommitする必要があるわけではなく、プロジェクトの規模やスピード感に応じて柔軟に変更できるので、今後Vuexを採用する場合はもっと柔軟に利用していきたいですね。

Vue/Vuexを利用してみて

SFCが分かりやすい

今回のプロジェクトはVue/VuexをSFC(Single File Component)で実装していったのですが、非常に書きやすかったです。
Vuexではviews(pages)で一つの画面の構成を表現し、そのviews内でいろいろなcomponentsを呼び出します。
views(pages)とcomponentsの関係やcomponentsの切り分け方など、下記のブログが非常に参考になりました。

aloerina01.github.io

SFCでコンポーネントを書くと、template script styleをすべて一つのファイル内で定義できるので、見通しがいいと感じました。styleはscopedにもできるので、セレクタの命名で機能ごとのプレフィックス等を考える必要もなく、シンプルな命名ができます。
個人的にはSFCでのコンポーネント実装が書きやすくて便利だと感じました。

storeのmodulesが名前空間で分かれている

storeのmodules化によってstoreを機能ごとに分割でき、さらにそのmodulesに名前空間を設定できるのも便利だと思いました。mutationの命名で被ってしまうことがあっても、namespacedなstoreなら問題ないです。

ハマったところ

名前空間で分けたstoreの引数付きgettersの呼び出し

storeにgettersを定義する際、引数付きのgetterを定義できます。
その引数付きのgetterをmapGettersで呼び出そうとした際にどうしても呼び出せなかったところで時間を使ってしまいました。
mapGettersではなく、普通に(this.$store.getters.getData(args)のように)呼び出すことで解決できます。
ただし、namespacedなstoreに定義したgettersの呼び出しは、this.$store.getters["namespace/getData"](args)といった書き方をします。
gettersのプロパティにアクセスする際に.ではなく[ ]を利用します。これはES2015の算出プロパティという構文で、[ ]で括られた部分が評価され、その値がプロパティ名になります。(この算出プロパティ、上述の例のmutationsにも利用されています。)
namespacedなstoreに定義されたgetterは"namespace/getData"というプロパティ名なので、[ ]を使わないとアクセスできないということです。
今回のプロジェクトで初めて知った構文だったのでちょっと戸惑いました。

こうしたほうがよかったと思うところ

TypeScriptの導入

今回のプロジェクトでは、ほぼ初学でVueを使い、プロダクトも小さいためJavaScript(ES2015)で実装しました。
しかし、主にstore(state)の設計をする際に型がほしいと強く思いました。
stateは単なるJavaScriptのObjectです。プロパティ名を決めても、入ってくる値を型で制限できないので、意図した型ではない値が入ってしまうことが考えられます。
単にSPA内でstateに値を追加するときもバグに気づきやすくなるので有用ですが、外部のサーバーと通信してJSONで取得したレスポンスをstateに追加するといったときなどに特に効果があると思います。
今回導入しなかったことの後悔を忘れず、次の機会があったら積極的にTypeScriptを導入しようと思います。

まとめ

Vue/Vuexを初めて利用しましたが、わかりやすい書き方で日本語のドキュメントがあるところが導入障壁を下げてくれているなと感じました。
とっつきやすさというのも重要な要素ですし、エコシステムが充実している、活発なコミュニティ活動がある、など盛り上がっている理由がよくわかります。
今後もSPAの開発プロジェクトで積極的に導入していきたいと思います。