"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

状態管理ライブラリ「Vuex」(store)とヘルパー関数を使ってみる/NUXT自己流チュートリアル(8)

今回は、 「Vue.js アプリケーションのための 状態管理パターン + ライブラリです」などと紹介されているVuexとヘルパの基本的な使い方を確認しつつ、画面遷移しても状態を表すデータを保持できていることを確認するデモを作ってみます。

f:id:arakan_no_boku:20190509225706p:plain

状態管理についてのおさらいから

 

状態とは、例えば。

  • 認証情報
  • 検索クエリ
  • フォームの入力情報
  • 個々のコンポーネントの動作の状態

などなど・・フロントエンドでページをまたいで管理すべきものです。

従来のWebアプリケーションの場合、画面の書き換え都度状態はリセットされます。

でも。

SPA( single-page application)の様な、画面を部分更新していくタイプのアプリケーションの場合、画面遷移しても状態がリセットされません。

それもあって。

規模が大きくなって管理すべき状態が増えたり、状態を更新するページが多岐にわたるようになると、非常に複雑になります。

複雑になると当然ながら状態管理は困難な仕事になります。

なので。

予測可能な方法によってのみ状態の変異を行うというルールを保証してくれる 状態管理パターン + ライブラリ「Vuex」の出番がでてくる・・というわけです。

vuex.vuejs.org

 

ざっくりとVuexの仕様を整理する

 

この図につきるわけですが。

f:id:arakan_no_boku:20190529223427p:plain

状態は「state(ステーツ)」に保存します。

この「state(ステーツ)」を変更する唯一の方法が「mutation(ミューテーション)」をコミットすることと決まってます。

ただし、この「mutation(ミューテーション)」は同期的である必要があります。

Vueコンポーネントはこの「state」を参照して処理をします。

で・・結果、状態を変更したいときは「Action(アクション)」を「dispatch(ディスパッチ)」します。

この「Action」が何をするかというと、「mutation(ミューテーション)」をコミットするわけです。

この「Action」は非同期の処理を含むことができます。

なんか、冗長に見えるのですが、「state」をクラスメンバ、「mutation」をセッター、「Action」がキューだと考えれば、複数スレッドの処理とかでは普通にする形ですから、複数のページから非同期に更新される「状態」を予測管理しやすい一方通行の更新で管理するには、当然の仕組・・じゃないのかなと思ってます。

で・・。

Nuxt.jsでは、このstateとかmutationを「store」フォルダで管理します。

 

簡単なチュートリアルをやってみます

 

前提など

 

自己流チュートリアル(1)で「前提」と「チュートリアルの準備」として書いていることは、できている必要があります。

つまり。

インストール・環境設定・テスト専用Chromeショートカットの作成うんぬんですね。

まだの方は、下記記事を先に参照して、環境設定をお願いします。

arakan-pgm-ai.hatenablog.com

あとは、続き物なのでNUXT自己流チュートリアル(7)までのソースを一部引き継ぐ形でやってますので、逐次参照ください。

arakan-pgm-ai.hatenablog.com

 

今回使うVuexのモードについて

 

NuxtでVuexを使うには2つのモードがあります。

  • クラッシックモード
  • モジュールモード

です。

でも、クラッシックモードはもう「非推奨」で「廃止予定」ですから、実際には「モジュールモード」一択です。

Nuxtをモジュールモードで動作させる条件は以下です。

  • storeフォルダに「index.js」が存在しない。
  • または、index.jsの中で「export default store」していない。

モジュールモードでは、storeフォルダにおいた「.js」ファイルの「ファイル名」の名前のモジュールとして名前空間に登録される仕様です。

そして「index.js」は、グローバルの名前空間に登録される・・というわけです。

今回は「index.js」を作らないパターンでやってみます。

 

ヘルパーも使います

 

Vuexには、便利なヘルパー関数があります。

vuex.vuejs.org

今回は以下の3つを使います。

  • mapState
  • mapMutations
  • mapActions

色んな使い方ができますが、一番スッキリとバインドを書けるやり方でやります。

使い方のポイントなどは、ソースの説明と一緒にします。

 

state・mutation・actionの定義

 

storeフォルダに、「infos.js」という名前で作ります。

store/infos.js

export const state = () => ({
    inActive: 0
  })
  
export const mutations = {
    setActiveState(state, inActive){
      state.inActive = inActive
    }
  }
  
export const actions = {
    toLeftSide({ commit }){
      commit('setActiveState', 1)
    },
    toRightSide({ commit }){
      commit('setActiveState', 2)
    },
    toDefault({ commit }){
        commit('setActiveState',0)
    }
  }

補足です。 

inActiveというstate(状態)を、初期値「0」で定義します。

mutationsで、stateを変更するセッターを定義します。

そして、actionsで、セッターを利用してstateの変更をcommitするメソッドを、3パターン定義しました。

  • state(状態)を「1」に変更する。
  • state(状態)を「2」に変更する。
  • state(状態)を「0」に戻す。

 の3つです。

これを、ページのVueコンポーネントの中で使っていきます。

 

vxdemo.vueで使う

 

pages/vxdemo.vueファイルを作ります。

4つのボックスを並べます。

そして。

  • state.inActiveが0:すべてのBOXがdark(アイコンが黒)
  • state.inActiveが1:左側のみBOXがActive(アイコンが紫)
  • state.inActiveが2:右側のみBOXがActive(アイコンが紫)

でコントロールします。

まずソースです。

pages/vxdemo.vue

<template>
    <div>
        <div class="container content has-text-centered">
            <NLink to="/">
                <p>ルートのページへ戻ります</p>
            </NLink>
        </div>
        <div class="tile is-ancestor">
            <div class="tile content is-parent is-vertical is-2"></div> 
            <div class="tile content  is-parent is-vertical is-2">
               <article class="tile is-child box">
                    <p class="title">Icon1</p>
                    <div class="block" v-if="inActive == 1">
                    <b-icon pack="fas" icon="tachometer-alt" size="is-large" type="is-primary"></b-icon>
                    <span class="subtitle">速度計</span>
                    </div>                    
                    <div class="block" v-else>
                    <b-icon pack="fas" icon="tachometer-alt" size="is-large" type="is-dark"></b-icon>
                    <span class="subtitle">・・・</span>
                    </div>                    
                </article>
            </div>    
            <div class="tile content is-parent is-vertical is-2">
               <article class="tile is-child  box">
                   <p class="title">Icon2</p>
                   <div class="block" v-if="inActive == 1">
                   <b-icon pack="fas" icon="tty" size="is-large" type="is-primary"></b-icon>
                   <span class="subtitle">電話</span>
                   </div> 
                   <div class="block" v-else>
                   <b-icon pack="fas" icon="tty" size="is-large" type="is-dark"></b-icon>
                   <span class="subtitle">・・・</span>
                   </div> 
                </article>
            </div>    
            <div class="tile content is-parent is-vertical is-2">
               <article class="tile is-child box">
                   <p class="title">Icon3</p>
                   <div class="block" v-if="inActive == 2">
                   <b-icon pack="fas" icon="angry" size="is-large" type="is-primary"></b-icon>
                   <span class="subtitle">怒り顔</span>
                   </div>
                   <div class="block" v-else>
                   <b-icon pack="fas" icon="angry" size="is-large" type="is-dark"></b-icon>
                   <span class="subtitle">・・・</span>
                   </div>
                </article>
            </div>    
            <div class="tile content is-parent is-vertical is-2">
               <article class="tile is-child box">
                   <p class="title">Icon4</p>
                   <div class="block" v-if="inActive == 2">
                   <b-icon pack="fas" icon="anchor" size="is-large" type="is-primary"></b-icon>
                   <span class="subtitle">イカリ</span>
                   </div>
                   <div class="block" v-else>
                   <b-icon pack="fas" icon="anchor" size="is-large" type="is-dark"></b-icon>
                   <span class="subtitle">・・・</span>
                   </div>
                </article>
            </div> 
            <div class="tile content is-parent is-vertical is-2"></div>    
        </div> 
        <div class="container content has-text-centered">
            <p>{{inActive}}</p>
        </div>
        <div class="container  has-text-centered">
            <b-button type="is-primary" v-on:click="toLeftSide">左を有効にする</b-button>
            <b-button type="is-primary" v-on:click="toRightSide">右を有効にする</b-button>
            <b-button type="is-primary" v-on:click="toDefault">初期状態にする</b-button>
        </div>   
    </div>
</template>
<script>
    import { mapState, mapMutations, mapActions } from 'vuex'

    export default {
        computed: {
            ...mapState('infos', ['inActive'])
        },
        methods: {
            ...mapMutations([]),
            ...mapActions('infos', [
                'toLeftSide',
                'toRightSide',
                'toDefault'
            ])
        }
    }
</script>

ポイントです。

まず、ヘルパーを使っている<script>~</script>内です。

<script>
    import { mapState, mapMutations, mapActions } from 'vuex'

    export default {
        computed: {
            ...mapState('infos', ['inActive'])
        },
        methods: {
            ...mapMutations([]),
            ...mapActions('infos', [
                'toLeftSide',
                'toRightSide',
                'toDefault'
            ])
        }
    }
</script>
    

ヘルパーをimportして、computedブロック内で「mapState」、methodsブロック内で「mapMutations」「mapActions」を定義しています。

(結局、mapMutationsは空定義になりましたが)

どちらも第一引数は「ファイル名=名前空間名」、第二引数にリストでそれぞれ定義した「state名」や「action名」を記述しています。

こうすることで、<templete></templete>内で、stateやactionをそのままの名前で参照できるようになります。

なので。

<div class="block" v-if="inActive == 1">

のように、stateを参照してIF条件として使えます。

同様にActionも。

<b-button type="is-primary" v-on:click="toLeftSide">左を有効にする</b-button>

のように、メソッドのようにイベントに対応して使えます。

 

画面遷移用に内部リンクも定義しておきます

 

画面遷移しても状態が維持されているのを確認するために、以下の内部リンクを追加した「index.vue」を用意しておきます。

 
<div class="container content has-text-centered">
    <NLink to="/vxdemo">
       <h3>状態管理のデモ</h3>
    </NLink>
</div>

index.vueは、内部リンクで画面遷移だけできればよいです。

 

さて実行してみます

 

プロジェクトフォルダをカレントにして「npm run dev」して、例によって「http://localhost:3000」でindex.vueを表示して、内部リンクで遷移します。

初期は「state==0」なので、すべてのアイコンがdark(黒)です。

f:id:arakan_no_boku:20190531233602p:plain

ここで「左を有効にする」ボタンを押します。

f:id:arakan_no_boku:20190531233736p:plain

stateが1になって、左側のアイコンがActive(紫)になります。

今度は「右を有効にする」ボタンを押します。

f:id:arakan_no_boku:20190531233850p:plain

state==2になるので、今度は右側のアイコンがActive(紫)になります。

ここで、上段の「ルートのページに戻ります」リンクでトップページに一旦戻ります。

f:id:arakan_no_boku:20190531234035p:plain

そして「状態管理のデモ」リンクで、先ほどの画面に戻ります。

f:id:arakan_no_boku:20190531234127p:plain

state==2が維持されていますね。

OKです。

実にシンプルかつ予測可能な形で状態管理ができてます。

Vuexは最初ややこしく感じますが、うまく使えれば、非常に便利ですねえ。

今回はこんなところで。

ではでは。