はじめに
本記事ではモーダルの管理について記載しますが、
他の「どこからでもユーザーアクションによって呼び出せる系のUI」は、基本的に同じ考え方で実装が可能だと思います。
本記事で書いたソースは以下にアップしています。
https://github.com/RikutoYamaguchi/vue-vuex-modal
実装はvue-cliをベースに行っています。
記事の内容とは関係ないので説明は省きますが、scssのコンパイルのために以下をインストールしています。
・"node-sass": "^4.5.3"
・"sass-loader": "^6.0.6"
追記:
さらに発展させたものを書きました。
Vue・Vuexでモーダルを管理する - その2
Vuexの導入
vue-cliによるスキャフォールディングにはVuexのインストールは含まれないので、はじめにVuexのインストールを行います。
$ yarn add vuex
Vuexのセットアップ
次にVuexをモジュールシステムで利用するための記述と、storeを作成するためにsrc/store/index.js
を作成します。
import Vue from 'vue'
import Vuex from 'vuex'
// モジュールシステムで使用するための記述
Vue.use(Vuex)
const state = {
}
const modules = {
}
const store = new Vuex.Store({
state,
modules
})
export default store
コンポーネントへ注入
また、すべてのコンポーネントでvuexを利用するために、先ほど作成したstoreを注入します。
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
+import store from './store'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
+ store,
template: '<App/>',
components: { App }
})
(Vuex導入の詳細は公式サイトをご参照ください。https://vuex.vuejs.org/ja/installation.html)
最も基本的なモーダル実装
それでは、最も基本的で簡単な実装を行い、モーダル実装の処理の流れを理解します。
モジュールの作成
簡単なモーダルを管理するためのモジュールを作成します。
const state = {
isShow: false
}
const mutations = {
show (state) {
state.isShow = true
},
hide (state) {
state.isShow = false
}
}
export default {
namespaced: true,
state,
mutations
}
stateのプロパティisShow
で「モーダルが表示されているかどうか」を管理します。
次に作成したモジュールをstoreに登録します。
import Vue from 'vue'
import Vuex from 'vuex'
+import basicModal from './modules/basicModal'
// モジュールシステムで使用するための記述
Vue.use(Vuex)
const state = {
}
const modules = {
+ basicModal
}
const store = new Vuex.Store({
state,
modules
})
export default store
これでモジュールが使用できるようになりました。
コンポーネントの作成
必要なコンポーネントの作成を行います。
src/App.vue
は初期状態から不要なものを全て削除した状態にしてスタートです。
<template>
<div id="app">
<button @click="basicModalShow">basicModalShow</button>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
name: 'app',
methods: {
...mapMutations('basicModal', {
basicModalShow: 'show'
})
}
}
</script>
<style lang="scss">
@import "./assets/style";
</style>
ここではmapMutations
を使い、basicModal
モジュールのshow
ミューテーションをbasicModalShow
という名前のメソッドとして登録し、basicModalShowボタンをクリックした時にbasicModalShow
メソッドを実行するように記述しています。
(mapMutations
の詳細はこちら→https://vuex.vuejs.org/ja/mutations.html)
次にモーダルのコンポーネントを作成します。
<template>
<transition name="fade">
<div class="modal" v-if="isShow">
<div class="modal__bg"></div>
<div class="modal__content">
<p>BasicModal</p>
<button @click="hide">closeModal</button>
</div>
</div>
</transition>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
name: 'BasicModal',
computed: {
...mapState('basicModal', ['isShow'])
},
methods: {
...mapMutations('basicModal', ['hide'])
}
}
</script>
モーダルのコンポーネントでは、mapState
とmapMutations
を使用します。
mapState
ではbasicModal
モジュールのisShow
ステートを参照できるようにします。
mapMutations
ではbasicModal
モジュールのhide
ミューテーションをメソッドとして登録し、closeModalボタンをクリック時に実行します。
またvueのアニメーション機能である、transitionラッパーコンポーネントを使用して、モーダル出現のフェードを実装しています。
(mapState
の詳細はこちら→https://vuex.vuejs.org/ja/state.html)
(transitionラッパーコンポーネントの詳細はこちら→https://jp.vuejs.org/v2/guide/transitions.html)
次に、モーダルコンポーネントをApp.vue
の子コンポーネントとして追加します。
<template>
<div id="app">
<button @click="basicModalShow">basicModalShow</button>
+ <basic-modal></basic-modal>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
+ import BasicModal from './components/BasicModal.vue'
export default {
name: 'App',
+ components: {
+ BasicModal
+ },
methods: {
...mapMutations('basicModal', {
basicModalShow: 'show'
})
}
}
</script>
<style lang="scss">
@import "./assets/style";
</style>
これで、基本的なモーダル実装は完了です。
basicModalShowボタンを押したらモーダルが出現し、closeModalを押したらモーダルが消えることが確認できます。
処理は以下のような流れができています。
■ モーダル表示
- basicModalShowボタンクリック
-
basicModal
モジュールのshow
ミューテーションを実行 -
isShow
ステートをtrue
に変更 - モーダルの表示
■ モーダル非表示
- closeModalボタンクリック
-
basicModal
モジュールのhide
ミューテーションを実行 -
isShow
ステートをfalse
- モーダルの非表示
しかし、この実装には複数モーダルを管理できない欠点があります。
モーダルの種類が一種類しかない場合は良いですが、そんなアプリケーションはなかなか存在しないですね。
この実装のまま複数のモーダルを管理しようとすると、モーダル毎にモジュール、ないしは表示状態のステート、状態を更新するミューテーションを作成する必要があります。
次は、この実装の欠点を克服する、複数種類のモーダルを管理する実装を行います。
複数種類のモーダルを管理する実装
さて、最も基本的なモーダル実装では基本的なモーダルのための状態管理を知ることができましたが、単一のモーダルに対してしか有効ではありませんでした。
それは「モーダルが表示されているかどうか」という状態を管理対象としたことで、モーダルが複数あった場合、それぞれにその状態を管理する必要が出てきてしまうからです。
そこで、複数種類のモーダルを管理する場合は、「ページ上で表示すべきモーダルの名称」を状態として捉え、実装を行います。
「モーダルの名称」を管理することで、現在表示すべきモーダルコンポーネントの差し替えを行うことが可能です。
モジュールの作成
早速、複数種類のモーダルを管理するためのモジュールを作成します。
const state = {
modalName: ''
}
const mutations = {
setModal (state, name) {
state.modalName = name
},
hideModal (state) {
state.modalName = ''
}
}
const actions = {
showModalTypeA ({ commit }) {
commit('setModal', 'ModalTypeA')
},
showModalTypeB ({ commit }) {
commit('setModal', 'ModalTypeB')
},
}
export default {
namespaced: true,
state,
mutations,
actions
}
最も簡単なモーダル実装では、state
のプロパティがisShow
でしたが、今回はmodalName
としました。
また、actions
を追加し、表示したいモーダルを呼び出せるようにしています。
(ミューテーションに直接モーダル名称を渡してもいいですが、コンポーネント側にモーダル名称を記述する必要があり、管理が大変になるのでアクションにモーダル名称を格納しました。)
忘れないうちに、storeへモジュールを登録します。
import Vue from 'vue'
import Vuex from 'vuex'
import basicModal from './modules/basicModal'
+import multiModal from './modules/multiModal'
// モジュールシステムで使用するための記述
Vue.use(Vuex)
const state = {
}
const modules = {
- basicModal
+ basicModal,
+ multiModal
}
const store = new Vuex.Store({
state,
modules
})
export default store
モーダル切り替えコンポーネントの作成
複数モーダルで一番重要な、モーダルを切り替えるコンポーネントを作成します。
<template>
<transition name="fade">
<component v-if="isShow" :is="modalName"></component>
</transition>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'MultiModalView',
computed: {
...mapState('multiModal', ['modalName']),
isShow () {
return this.modalName !== ''
}
}
}
</script>
ここでは、vueの機能である「動的コンポーネント」を使用しています。
動的コンポーネントは:isに対してコンポーネント名を代入することで、そのコンポーネントを呼び出す機能があるので、そこへモーダルの名称を代入することで、モーダルの切り替えを行います。
(動的コンポーネントの詳細はこちら→https://jp.vuejs.org/v2/guide/components.html#動的コンポーネント)
computedのisShowでは、multiModal
モジュールのmodalName
ステートの値が空文字かどうかを判断し、動的コンポーネントを表示するかどうかの制御を行っています。
App.vueの子コンポーネントとして追加するのを忘れずに。
<template>
<div id="app">
<button @click="basicModalShow">basicModalShow</button>
<basic-modal></basic-modal>
+ <multi-modal-view></multi-modal-view>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import BasicModal from './components/BasicModal.vue'
+ import MultiModalView from './components/MultiModalView.vue'
export default {
name: 'App',
components: {
- BasicModal
+ BasicModal,
+ MultiModalView
},
methods: {
...mapMutations('basicModal', {
basicModalShow: 'show'
})
}
}
</script>
<style lang="scss">
@import "./assets/style";
</style>
モーダルコンポーネントの作成
それではサクッとモーダルコンポーネントを用意しましょう。
<template>
<div class="modal">
<div class="modal__bg"></div>
<div class="modal__content">
<p>ModalTypeA</p>
<button @click="hideModal">closeModal</button>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import MultiModalMixin from '../mixins/MultiModalMixin'
export default {
name: 'ModalTypeA',
mixins: [MultiModalMixin]
}
</script>
<template>
<div class="modal">
<div class="modal__bg"></div>
<div class="modal__content">
<p>ModalTypeB</p>
<button @click="hideModal">closeModal</button>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import MultiModalMixin from '../mixins/MultiModalMixin'
export default {
name: 'ModalTypeB',
mixins: [MultiModalMixin]
}
</script>
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations('multiModal', ['hideModal'])
}
}
multiModal
モジュールで用意した2つのアクションに対応するモーダルコンポーネントを作成しました。
multiModal
モジュールのhideModal
ミューテーションは両モーダルとも共通の記述になるのでmixinで登録します。
最も簡単なモーダル実装ではモーダルコンポーネントにtrainsitionでアニメーションを実装していましたが、この実装ではモーダル切り替えコンポーネントでアニメーションさせているので、モーダルコンポーネントには不要となります。
これらをモーダル切り替えコンポーネントの子コンポーネントへ追加します。
<template>
<transition name="fade">
<component v-if="isShow" :is="modalName"></component>
</transition>
</template>
<script>
import { mapState } from 'vuex'
+ import ModalTypeA from './ModalTypeA.vue'
+ import ModalTypeB from './ModalTypeB.vue'
export default {
name: 'MultiModalView',
+ components: {
+ ModalTypeA,
+ ModalTypeB,
+ },
computed: {
...mapState('multiModal', ['modalName']),
isShow () {
return this.modalName !== ''
}
}
}
</script>
呼び出しの実装
最後にモーダルを呼ば出すボタンをApp.vue
へ追加します。
<template>
<div id="app">
<button @click="basicModalShow">basicModalShow</button>
+ <button @click="showModalTypeA">showModalTypeA</button>
+ <button @click="showModalTypeB">showModalTypeB</button>
<basic-modal></basic-modal>
<multi-modal-view></multi-modal-view>
</div>
</template>
<script>
- import { mapMutations } from 'vuex'
+ import { mapActions, mapMutations } from 'vuex'
import BasicModal from './components/BasicModal.vue'
import MultiModalView from './components/MultiModalView.vue'
export default {
name: 'App',
components: {
BasicModal,
MultiModalView
},
methods: {
...mapMutations('basicModal', {
basicModalShow: 'show'
- })
+ }),
+ ...mapActions('multiModal', ['showModalTypeA', 'showModalTypeB'])
}
}
</script>
<style lang="scss">
@import "./assets/style";
</style>
これで、ModalTypeAボタンとModalTypeBボタンを押したときに、それぞれのモーダルが表示されることが確認できます。
おわりに
複数種類のモーダルを管理する実装は、冒頭でも延べたように他の「どこからでもユーザーアクションによって呼び出せる系のUI」にも応用ができます。
例えばstateへクリックされた位置を追加して、コンテキストメニューの管理をしたり。
また、モーダルの次のアクションとして他のモーダルを表示するときは、モーダルコンポーネントへ次のモーダルを呼び出すアクションを追加することで可能です。
次はコンテキストメニューの実装か、vue-routerを用いたモーダルの実装を書いてみようと思います。
ということで、最後まで閲覧いただきありがとうございました。