Edited at

Vue・Vuexでモーダルを管理する

More than 1 year has passed since last update.


はじめに

本記事ではモーダルの管理について記載しますが、

他の「どこからでもユーザーアクションによって呼び出せる系の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を作成します。


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を注入します。


src/main.js

// 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)


最も基本的なモーダル実装

それでは、最も基本的で簡単な実装を行い、モーダル実装の処理の流れを理解します。


モジュールの作成

簡単なモーダルを管理するためのモジュールを作成します。


src/store/modules/basicModal.js

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に登録します。


src/store/index.js

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は初期状態から不要なものを全て削除した状態にしてスタートです。


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)

次にモーダルのコンポーネントを作成します。


src/components/BasicModal.vue

<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>


モーダルのコンポーネントでは、mapStatemapMutationsを使用します。

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の子コンポーネントとして追加します。


src/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を押したらモーダルが消えることが確認できます。

処理は以下のような流れができています。

■ モーダル表示

1. basicModalShowボタンクリック

2. basicModalモジュールのshowミューテーションを実行

3. isShowステートをtrueに変更

4. モーダルの表示

■ モーダル非表示

1. closeModalボタンクリック

2. basicModalモジュールのhideミューテーションを実行

3. isShowステートをfalse

4. モーダルの非表示

しかし、この実装には複数モーダルを管理できない欠点があります。

モーダルの種類が一種類しかない場合は良いですが、そんなアプリケーションはなかなか存在しないですね。

この実装のまま複数のモーダルを管理しようとすると、モーダル毎にモジュール、ないしは表示状態のステート、状態を更新するミューテーションを作成する必要があります。

次は、この実装の欠点を克服する、複数種類のモーダルを管理する実装を行います。


複数種類のモーダルを管理する実装

さて、最も基本的なモーダル実装では基本的なモーダルのための状態管理を知ることができましたが、単一のモーダルに対してしか有効ではありませんでした。

それは「モーダルが表示されているかどうか」という状態を管理対象としたことで、モーダルが複数あった場合、それぞれにその状態を管理する必要が出てきてしまうからです。

そこで、複数種類のモーダルを管理する場合は、「ページ上で表示すべきモーダルの名称」を状態として捉え、実装を行います。

「モーダルの名称」を管理することで、現在表示すべきモーダルコンポーネントの差し替えを行うことが可能です。


モジュールの作成

早速、複数種類のモーダルを管理するためのモジュールを作成します。


src/store/modules/multiModal.js

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へモジュールを登録します。


src/store/index.js

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



モーダル切り替えコンポーネントの作成

複数モーダルで一番重要な、モーダルを切り替えるコンポーネントを作成します。


src/components/MultiModalView.vue

<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の子コンポーネントとして追加するのを忘れずに。


src/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>


モーダルコンポーネントの作成

それではサクッとモーダルコンポーネントを用意しましょう。


src/components/ModalTypeA.vue

<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>



src/components/ModalTypeB.vue

<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>



src/mixins/MultiModalMixin.js

import { mapMutations } from 'vuex'

export default {
methods: {
...mapMutations('multiModal', ['hideModal'])
}
}

multiModalモジュールで用意した2つのアクションに対応するモーダルコンポーネントを作成しました。

multiModalモジュールのhideModalミューテーションは両モーダルとも共通の記述になるのでmixinで登録します。

最も簡単なモーダル実装ではモーダルコンポーネントにtrainsitionでアニメーションを実装していましたが、この実装ではモーダル切り替えコンポーネントでアニメーションさせているので、モーダルコンポーネントには不要となります。

これらをモーダル切り替えコンポーネントの子コンポーネントへ追加します。


src/components/MultiModalView.vue

<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へ追加します。


src/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を用いたモーダルの実装を書いてみようと思います。

ということで、最後まで閲覧いただきありがとうございました。