Vue.js+VuexでFlashメッセージコンポーネントのサンプルを書いてみました

今回はVuexのお勉強もかねて、FlashメッセージをVue.js+Vuexで書いてみました。 というのも、今開発をすすめている「みんなでつくるダンジョン」はVue.jsを使って作っているのですが、「保存しました!」とか「通信中です」などのメッセージを出したくなったのでした。

つくったもののデモは以下にあります。リンク先に「Show Flash Message」ボタンが3つあるので押してみてください。

https://piyoppi.github.io/samples/vue-flash-message-sample/

このようなメッセージはいろいろなコンポーネントから呼び出したくなったりするのですが、各コンポーネントから$emitして値のやり取りをするのは大変です。 ということで、Vuexに慣れる意味も込めてVuexで作ってみることにしました。

Vuexについては本家日本語版ドキュメントに詳しくあるので、これを読んで理解しました。(日本語ドキュメントありがたや~)。 Vue.jsはコンポーネントに状態を持てますが、ものによっては状態を共有したい場合も出てきます。propsやemitをつかっての状態共有もできないことはないですが、規模が大きくなると状態の紐づきを管理するのが大変になるので、共有すべき状態について管理するためにつかうライブラリがVuex(という理解)です。

vuex.vuejs.org

Vuexには「State」「Getter」「Mutation」「Action」の4つの要素があり、それぞれ以下のような役割があります(という理解をしています)。

  • State: 共有したい状態(変数)を保持するところ
  • Getter: Stateを加工して返すことができるもの(Stateを足したり割ったり文字列連結したりした値を欲しい時に使う)
    • 特に加工が必要ない場合は直接Stateから値をとることもできる
    • 今回は使っていません
  • Mutation: Stateを更新することができるメソッドたち
    • こいつが唯一Stateを更新できる
    • 更新したいときは this.$store.commit('Mutation名', 値); みたいにする
  • Action: Mutationを呼び出して何らかの処理をするためのメソッドたち
    • Action内でMutationを呼び出して状態を更新したりする
    • Actionを呼び出すときは this.$store.dispatch('Action名', 値); みたいにする
    • Mutationでは非同期処理ができないが、Actionではできる(ここがポイントみたい)
    • 単純なStateの更新ならMutation経由でもできるし、複雑な(?)処理を経てStateを更新したいときはMutationを呼び出す命令を書いたActionを実行する

基本的にVuexのこれらの要素にはどのコンポーネントからも簡単にアクセスできるようにすることができます。 また、Stateが変更されれば各コンポーネントでStateを参照している箇所も更新されるので、State(状態)の共有ができます。

今回のサンプルソースはこちら。 github.com

VuexのActionをつかって表示メッセージを受け取る

FlashメッセージにつかうState,Mutation,Actionはこのように定義しました。showFlashMessage をディスパッチすることで、必要な値がmutation経由でstateに反映されます。

# src/components/noticeMessageBox/noticeMessageBoxStore.js

export default {
  state: {
    text: '',
    mode: 'processing',
    visible: false,
    timeoutId: -1,
  },
  mutations: {
    setMessage: (state, payload) => {
      state.text = payload.text;
      state.mode = payload.mode;
      state.visible = true;
    },
    setMessageVisible: (state, value) => state.visible = value,
    setMessageTimeoutId: (state, value) => state.timeoutId = value,
    clearMessageTimeoutId: (state) => state.timeoutId = -1,
  },
  actions: {
    showFlashMessage: ({state, commit}, message) => new Promise((resolve, reject) => {
      //timeoutId !== 1 のときはVisibleを変更するsetTimeoutが生きているのでキャンセルする
      if( state.timeoutId !== -1 ) {
        clearTimeout(state.timeoutId);
        commit('clearMessageTimeoutId');
      }

      commit('setMessage', message);

      if( message.duration > 0 ) {
        //durationだけ時間が経ったらVisible=falseとする(メッセージを隠す)
        const timeoutId = setTimeout( () => {
          commit('clearMessageTimeoutId');
          commit('setMessageVisible', false);
          return resolve();
        }, message.duration);
        commit('setMessageTimeoutId', timeoutId);
      } else {
        return resolve();
      }
    })
  }
}

上記定義をVuexオブジェクトをインスタンス化する際に読み込んであげます。

# src/main.js

import Vue from 'vue'
import Vuex from "vuex"
Vue.use(Vuex);
import noticeMessageBoxStore from "./components/noticeMessageBox/noticeMessageBoxStore.js"
import appOptions from './main.vue'
let store = new Vuex.Store({
modules: {
    noticeMessageBoxStore
  }
});

// src/main.vueをdist/index.htmlのapp-entrypointにマウントします
window.addEventListener("load", ()=>{
  const app = Object.assign(appOptions, {
    el: '#app-entrypoint',
    store: store  //storeを注入する(後述)
  });
  new Vue(app);
}, false);

使うときのイメージはこんな感じ。textにメッセージを設定し、Durationに表示時間(ミリ秒)を設定します。modeは見た目を変更するための文字列(後述)です。

this.$store.dispatch('showFlashMessage', {text: 'Message!', duration: 4000, mode: 'done'});

Flashメッセージコンポーネントの定義はこんな感じ。先ほどのstoreを見て、変更がリアルタイムにコンポーネントに反映されるようにしています。表示・非表示切り替えはcomputedに記したスタイルで定義します。 (this.$storeをつかうには、ルートコンポーネントでstoreを注入する必要があります。)

storeのmodeに応じて適用するスタイルシートのクラスを変えることで、Notice、Warning、Errorなどの見た目を変えることができます。

# src/components/noticeMessageBox/noticeMessageBox.vue

<template>
<div class="msgbox-outer" :class="outerClass">
    {{ $store.state.noticeMessageBoxStore.text }}
  </div>
</template>

<script>
export default {
  computed: {
    mode: function() {
      return this.$store.state.noticeMessageBoxStore.mode;
    },
    visible: function() {
      return this.$store.state.noticeMessageBoxStore.visible;
    },
    outerClass: function() {
      return {
        darkblue: this.mode === "processing",
        lightblue: this.mode === "done",
        red: this.mode === "error",
        hidden_bottomside: !this.visible && !this.isTopside,
        hidden_topside: !this.visible && this.isTopside,
        show_bottomside: this.visible && !this.isTopside, 
        show_topside: this.visible && this.isTopside,
        topside: this.isTopside
    }
  },
  //(以下略)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             </script>                   

あとは、コンポーネントのテンプレートに以下のように書き、componentsにコンポーネントを登録してあげればどのコンポーネントからもFlashメッセージを表示させることができるようになります。

# src/main.vue

<notice-messagebox></notice-messagebox>

こんなかんじにVuexとVue.jsをくみあわせて作ることができました。もうちょいとVuexと戯れて、開発の効率が良くなると良いなぁとおもっています。 ではではー。

「ここちがうよ!」とか「ここはこうじゃないの?」みたいな建設的な技術的ツッコミは歓迎しますので、ぜひー。(わたしも自信があるわけではないので。。。)