Vuexで中~大規模なSPAを構築する【Vue.js】

そもそもVue.jsとは?

「Vue.jsまたはVueは、Webアプリケーションにおけるユーザーインターフェイスを構築するための、オープンソースのJavaScriptフレームワークである(Wikipedia)。」

VueはJavaScriptのフレームワークで、HTML、CSS、JavaScriptの書き方を知っていれば簡単に高機能なアプリケーションを作ることができます。

何故Vuexを使うのか?

VueSPA(シングルページアプリケーション)を構築する際、プロジェクトの規模が肥大化していくとコンポーネント間のデータの受け渡しが複雑になり管理が難しくなります。

例えばコンポーネントが多数存在し、コンポーネント間で受け渡すパラメータをpropsemitで何度も受け渡しするとパラメータの流れを追うことが難しくなります。
Vuex
を使うことでデータの管理を一元化することができ、規模の大きなプロジェクトでのデータ管理が容易となります。

VuexWEBアプリを構築してみよう

  1. VueVuexのインストール
    Vue CLIを利用して初期環境の構築を行います。Vue CLIをインストールするためにnpmを使用します(npmを使用するにはNode.jsをインストールする必要がありますが今回は割愛します)

    Vue CLIのインストール

    npm install -g @vue/cli

    下記コマンドを打ってバージョンが表示されれば成功です。

    vue –version
    4.1.2
    

    プロジェクト用のでディレクトリに移動し、下記コマンドを打ってVueプロジェクトを作成します。

    vue create sample-app

    Manually select featuresを選択

    Vue CLI v4.1.2
    ┌──────────────────────────────────────────┐
    │                                          │
    │   New version available 4.1.2 → 4.5.13   │
    │     Run npm i -g @vue/cli to update!     │
    │                                          │
    └──────────────────────────────────────────┘
    
    ? Please pick a preset: 
      default (babel, eslint) 
    ❯ Manually select features 

    Vuexを選択

    ? Please pick a preset: Manually select features
    ? Check the features needed for your project: 
     ◉ Babel
     ◯ TypeScript
     ◯ Progressive Web App (PWA) Support
     ◯ Router
    ❯◉ Vuex
     ◯ CSS Pre-processors
     ◉ Linter / Formatter
     ◯ Unit Testing
     ◯ E2E Testing
    

    下記コマンドを打ちます。

    cd sample-app
    npm run serve
    

    下記の表示が出たらブラウザでhttp://localhost:8080/にアクセスすると画面が表示されます。

      App running at:
      - Local:   http://localhost:8080/ 
      - Network: http://xx.x.xxx.xx:8080/
    
      Note that the development build is not optimized.
      To create a production build, run npm run build.
  2. StoreVuexの状態を管理する
    Vuexではstoreでデータを管理します。src/store/index.jsの下記の記述でVuexの設定を行っていきます。Storeでの設定はどのコンポーネントからもアクセス可能になります。
    ※modulesは今回使用しません。

    export default new Vuex.Store({
      state: {},
      mutations: {},
      actions: {},
      modules: {},
    });
    
  3. stateで変数を保持する

    storeを設定していきます。statestoreの基本となるプロパティで、グローバル変数のようにどのコンポーネントからもアクセス可能な変数を定義することができます。
    src/store/index.js stateを書き換えます。

    export default new Vuex.Store({
      state: {
    	number: 1
    },
      mutations: {},
      actions: {}
    });
    

    src/App.vueの下記の記述は不要なので削除します。

    <img alt="Vue logo" src="./assets/logo.png" />

    src/components/HelloWorld.vueを書き換えます。this.$store.state をComputedプロパティに記述してstateにアクセスします。

    <div>
       <p>{{number}}</p>
     </div>
    </template>
     
    <script>
    export default {
     computed:{
       number(){
         return this.$store.state.number;
       }
     }
    };
    </script>
    <style scoped>

    “1”が表示されます。

  4. gettersでstateの値を算出する
    gettersはVuexで算出プロパティのように使用できます。Computedプロパティのように計算した値を返すような使い方ができます。src/store/index.jsgettersを追加します。

    export default new Vuex.Store({
     state: {
       number: 1
     },
     getters: {
       numberAdd: state => state.number + 10
     },
     mutations: {},
     actions: {}
    });
    

    src/components/HelloWorld.vueを書き換えます。

    <template>
     <div>
       <p>{{number}}</p>
     </div>
    </template>
     
    <script>
    export default {
     computed:{
       number(){
         return this.$store.getters.numberAdd;
       }
     }
    };
    </script>
    <style scoped>
    </style>
    

    this.$store.gettersでgettersを呼び出すことができます。
    ”11”が表示されます。

    $store.state.number+10 のようにしても同じように表示できますが、$store.stateではstateの値を変更できてしまいます。一方gettersでは参照のみですので書き換える心配はありません。Stateの値を参照したいだけの場合は基本的にgettersを使うことが推奨されています。
    mutationsでstateの値を更新する

  5. mutationsstateの値を更新する
    Stateの値を更新したい場合、$store.stateではなくmutationsを使います。$store.stateを使用して更新すると、どこで更新されているか追跡するのが困難になるため、Vuexstateの更新を行う場合はmutationsで行うのが原則です。

    src/store/index.jsを書き換えます。

    export default new Vuex.Store({
     state: {
       number: 1
     },
     getters: {
       numberAdd: state => state.number + 10
     },
     mutations: {
       incrementNumber(state,num){
         state.number += num;
       }
     },
     actions: {}
    });

    mutationsでは第1引数にstate、第2引数に好きな値をとることができます。今回はインクリメントに使用する数値をとります。

    src/components/HelloWorld.vueを書き換えます。

    <template>
     <div>
       <button @click="incrementNumber">+5</button>
       <p>{{number}}</p>
     </div>
    </template>
     
    <script>
    export default {
     computed:{
       number(){
         return this.$store.state.number+10;
       }
     },
     methods:{
       incrementNumber(){
         this.$store.commit("incrementNumber",5);
       }
     }
    };
    </script>
    <style scoped>
    </style>
    

    MethodsにincrementNumberを定義し、その中でthis.$store.commitと記述することでmutationsを呼び出します。第1引数にmutationsのプロパティ名を、第2引数にmutations のincrementNumberに渡す引数を設定します。
    ボタンのクリックイベントにincrementNumberを定義すると、クリックで数値が加算されます

  6. actionで非同期処理を行う

    mutationsでは非同期処理を扱えないため、非同期処理を行たい場合はactionsを使用します。src/store/index.jsを書き換えます。

    export default new Vuex.Store({
     state: {
       number: 1
     },
     getters: {
       numberAdd: state => state.number + 10
     },
     mutations: {
       incrementNumber(state,num){
         state.number += num;
       }
     },
     actions: {
       incrementNumber(context,number){
         setTimeout(() => {
           context.commit('incrementNumber',number)
         }, 4000);
       }
     }
    });
    

    actionsにincrementNumberを定義します。actionsの第一引数のcontextはcontext.commitと記述してmutationを呼び出したり、context.stateでstateにアクセスできたりします。

    今回はsetTimeoutで非同期処理を作り、その中でcontext.commitを記述してmutationのincrementNumberを呼び出します。

    src/components/HelloWorld.vueを書き換えます。

    <template>
     <div>
       <button @click="incrementNumber">+5</button>
       <p>{{number}}</p>
     </div>
    </template>
     
    <script>
    export default {
     computed:{
       number(){
         return this.$store.state.number+10;
       }
     },
     methods:{
       incrementNumber(){
         this.$store.dispatch("incrementNumber",5);
       }
     }
    };
    </script>
    <style scoped>
    </style>
    

    this.$store.dispatchと記述することでactionを呼び出すことができます。

    ボタンをクリックすると4秒後に数値が加算され、非同期処理が行われることがわかります。

Vuexの全体像、データフローについて

データフローは下記の図にようになっています。コンポーネントからActionsまたmutationsstateの値を更新し、図には書かれていないですがgettersを使ってコンポーネントにstateの値を表示するといったフローが基本となります。

Smallitのサービス