モーダルを開いた状態で画面遷移をすると予期せぬ動作をすることがある
ktsnさんが開発したvue-thin-modalはとても便利なUIライブラリですが
モーダルを表示した状態で画面遷移すると予期せぬ動作をすることがあります。
vue-thin-modalを使う場合、レイアウトファイルにmodal-portal
コンポーネントを配置し、モーダルを表示するページのコンポーネントにmodal
コンポーネントを置くことになると思います。Nuxt.jsの場合だと以下のような感じになると思います。
- レイアウト
<template>
<main>
<nuxt />
<modal-portal />
</main>
</template>
<script>
// ...
</script>
<style>
/* ... */
</style>
- ページコンポーネント
<template>
<div>
<button @click="$modal.push('someModal')">Open modal</button>
<modal name="someModal">
open!
</modal>
</div>
</template>
<script>
// ...
</script>
<style>
/* ... */
</style>
ページコンポーネントのボタンをクリックすると、$modal.push('someModal')
が呼ばれてvue-thin-modalの内部にスタックされ
レイアウト側のmodal-portalコンポーネントにmodalコンポーネントのコンテンツが表示されます。
ここでモーダルを開いたままブラウザバックなどで画面遷移をすると、ページコンポーネントと共にモーダルのコンテンツは破棄されます。しかし、vue-thin-modalは画面遷移を知らないので、内部にはスタックが残ったままになってしまいます。この結果、モーダルのコンテンツが無いのにも関わらず、モーダル表示中のCSSクラスがHTMLに残ってしまい、最悪の場合アプリケーションが操作不能になります。
とりあえず画面遷移が発生した場合には全てのモーダルを閉じる
画面遷移時の予期せぬ動作を防ぐには、モーダルのコンテンツが破棄される時に$modal.pop()
を明示的に呼んであげればよいです。
ページ間でモーダルを兼用することは滅多にないと思うので、画面遷移時に表示中の全モーダルを破棄するようにします。
Nuxt.jsのレイアウトファイルにモーダル破棄の処理を追加した例を紹介します。
<script>
import { Component, Vue } from 'nuxt-property-decorator'
@Component
export default class DefaultLayout extends Vue {
unregisterBeforeEach = () => {}
created() {
// MEMO: beforeEachは登録解除の関数を返す実装になっている
// https://github.com/vuejs/vue-router/blob/ea8cb474f869a5a12a095fcb5989c45c68971d14/src/index.js#L121-L123
// https://github.com/vuejs/vue-router/blob/ea8cb474f869a5a12a095fcb5989c45c68971d14/src/index.js#L217-L223
this.unregisterBeforeEach = this.$router.beforeEach((_, __, next) => {
popAllModals(this.$modal)
next()
})
}
beforeDestroy() {
this.unregisterBeforeEach()
}
}
function popAllModals(modalMediator) {
(function factorial() {
if (modalMediator.currentName) {
modalMediator.pop()
factorial()
}
})()
}
</script>
各画面の遷移時にモーダル削除の処理を実行したいので$router.beforeEach
フックからpopAllModals
を実行します。$router.beforeEach
の戻り値はフック解除用の関数なので、これを状態として保持しておきます。レイアウトファイルが破棄されるときには先に保存したフック解除用の関数を実行し、beforeEachのフックも解除します。
popAllModals
では、$modal.currentName
がundefined
になるまでモーダルを削除します。
これらの処理により、画面遷移時に全てのモーダルを閉じることが可能になりました。
まとめ
vue-thin-modalでモーダルを開いた状態で画面遷移をしても、予期した動作をするようになりました。
この記事ではNuxt.jsを例に用いていますが、おそらく通常のVueアプリケーションでも使えると思います。