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には「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と戯れて、開発の効率が良くなると良いなぁとおもっています。 ではではー。
「ここちがうよ!」とか「ここはこうじゃないの?」みたいな建設的な技術的ツッコミは歓迎しますので、ぜひー。(わたしも自信があるわけではないので。。。)