後なに喋ってないかな...ネタ切れも近い...![]()
と思ったのですが、まだイベントのサイクルについて記事を書いていなかったですね。
定義されているイベント
Inertia では以下の router イベントが埋め込まれています。
- before
- start
- progress
- success
- flash
- error
- invalid
- exception
- finish
- navigate
- prefetching
- prefetched
上記のイベント router.on() や document.addEventlistener() で傍受することができます。
// これらはすべて同じ意味
import { router } from '@inertiajs/vue3'
// (1) グローバルに傍受
router.on('start', (e) => {
console.log(e)
})
// (2) 各処理でハンドリングを差し込む
rouer.visit('<url>', {
onStart: (e) => {
console.log(e)
}
})
// (3) (1) は document からも傍受できる
document.addEventListener('inertia:start', (e) => {
console.log(e);
})
これらは Vue やブラウザのサイクルとどのように関わっているのでしょうか。
大事なイベント
長くなるので先にまとめます!
大事なものは大きく二つ!
- ページ遷移が起きた時は
navigateが発火 - 通信が発生したときは
before-start-success/error-finishの順で発火
表にするとこのような形です。
| シナリオ | [window] load |
[window] popstate |
[vue] mounted |
inertia: navigate |
inertia: start |
|---|---|---|---|---|---|
| 初回訪問 | 〇 | 〇 | 〇 | ||
| ページ遷移 (Link) |
〇 | 〇 | 〇 | ||
| 「戻る」「進む」 | 〇 | 〇 | 〇 | ||
| フォームの送信 (preserveState: true) |
〇 | ||||
| ページの更新 (preserveState: true) |
〇 | ||||
| ページの更新 (preserveState: false) |
〇 | 〇 |
挙動を確認してみる
では物理的にイベントを console.log でキャプチャして、挙動を確認します。
以下のプラグインを作成して app.ts に紐づけます。
結局愚直に確認するのがかんたん!
明らかに邪魔になるイベントはコメントアウトしています。
参考記事はこちら。
- https://developer.mozilla.org/ja/docs/Web/API/Window
- https://developer.mozilla.org/ja/docs/Web/API/Document
- https://ja.vuejs.org/api/composition-api-lifecycle
- https://inertiajs.com/docs/v2/advanced/events
import { App } from 'vue'
export const listenerPlugin = {
install(app: App) {
// (1) Window で傍受するイベント
const windowEvents = [
'afterprint',
'appinstalled',
'beforeinstallprompt',
'beforeprint',
'beforeunload',
'blur',
// 'devicemotion', 邪魔
// 'deviceorientation', 邪魔
// 'deviceorientationabsolute', 邪魔
'error',
// 'focus', 本質ではない
'gamepadconnected',
'gamepaddisconnected',
'hashchange',
'languagechange',
'load',
// 'message', 多すぎ
'messageerror',
'offline',
'online',
// 'orientationchange',
'pagehide',
'pagereveal',
'pageshow',
'pageswap',
'popstate',
'rejectionhandled',
'resize',
// 'scrollsnapchange',
// 'scrollsnapchanging',
'storage',
'unhandledrejection',
// 'unload', 権限なし
// 'vrdisplayactivate',
// 'vrdisplayconnect',
// 'vrdisplaydeactivate',
// 'vrdisplaydisconnect',
// 'vrdisplaypresentchange',
]
for (const eventName of windowEvents) {
window.addEventListener(eventName, () => {
console.log(`[window] ${eventName}`);
})
}
// (1) Document で傍受するイベント
const documentEvents = [
// 'afterscriptexecute',
// 'beforescriptexecute',
'DOMContentLoaded',
'fullscreenchange',
'fullscreenerror',
'pointerlockchange',
'pointerlockerror',
// 'prerenderingchange',
'readystatechange',
'scroll',
'scrollend',
// 'scrollsnapchange',
// 'scrollsnapchanging',
'securitypolicyviolation',
'selectionchange',
'visibilitychange',
'inertia:before',
'inertia:start',
'inertia:progress',
'inertia:success',
'inertia:flash',
'inertia:error',
'inertia:invalid',
'inertia:exception',
'inertia:finish',
'inertia:navigate',
'inertia:prefetching',
'inertia:prefetched',
]
for (const eventName of documentEvents) {
document.addEventListener(eventName, () => {
console.log(`[document] ${eventName}`);
})
}
// (3) Vue で傍受するイベント(うるさいので消してもよい)
const vueHooks = [
'beforeCreate',
'beforeMount',
'mounted',
// 'beforeUpdate', // うるさい
// 'updated', // うるさい
'beforeUnmount',
'unmounted',
'errorCaptured',
// 'renderTracked', // うるさい
// 'renderTriggered', // うるさい
'activated',
'deactivated',
'serverPrefetch',
]
const mixin: Record<string, any> = {}
for (const hook of vueHooks) {
mixin[hook] = function () {
console.log(`[vue] ${hook}`)
}
}
app.mixin(mixin)
}
}
+ import { listenerPlugin } from '@/listenerPlugin'
createInertiaApp({
title: (title) => (title ? `${title} - ${appName}` : appName),
resolve: (name) =>
resolvePageComponent(
`./pages/${name}.vue`,
import.meta.glob<DefineComponent>('./pages/**/*.vue'),
),
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
+ .use(listenerPlugin)
.use(plugin)
.mount(el);
},
progress: {
color: '#4B5563',
},
});
今回はじめて Vue のプラグインを作ったんですけど、こんな簡単なのですか...。
イベントの発行順序
ではそれぞれの場合を見ていきます。
[vue]のイベントは、全コンポーネント分発火するので、適当に端折ります。
laytou と page だけ、とかには絞れ無さそうだった...。
また状況によって再マウントされたり、使いまわされてたりするから、あくまでも参考程度でお願いします!
▼ ページを初めて表示したとき
Vue のコンポーネントがマウントされてから inertia:navigate が発行されるみたい。
初回通信は HTML の中に Props も含まれるため、inertia:start 系の通信は走らない。
[vue] beforeCreate // コンポーネントの再構築
[vue] beforeMount
[vue] mounted
[document] DOMContentLoaded // 速すぎて出ない時もある...
[document] inertia:navigate // 遷移完了
[document] readystatechange
[window] load
[window] pageshow
▼ ページを遷移したとき
まず inertia:before が発火する。
その後 Vue のコンポーネントがマウントされ、同様に inertia:navigate が発火される。
最後に inertia:finish が発火する。
Inertia 本体の通信は router や useForm にある OnSuccess OnFinish が発火するので、ページ遷移したあとも処理が残ってるんですねぇ。
[document] inertia:before
[document] inertia:start
[vue] beforeUnmount // コンポーネントの再構築
[vue] beforeCreate
[vue] beforeMount
[vue] unmounted
[vue] mounted
[document] inertia:navigate // 遷移完了
[document] inertia:success
[document] inertia:finish
▼ ブラウザの「戻る」を押したとき
window.history.back() と同義。
inertia:start が発火しないことから分かる通り、Inertia ではページを戻ってもデータ通信は行いません。History に記録されているデータを読み出します。
(一応 MAX 18MB だったっけな)
もしデータの再読み込みが必要な場合は、reload() なりの処理を埋め込み必要があります。
[window] popstate
[vue] beforeUnmount // コンポーネントの再構築
[vue] beforeCreate
[vue] beforeMount
[vue] unmounted
[vue] mounted
[document] inertia:navigate
ちなみに「進む」も同じ挙動です。
▼ フォームを送信したとき(成功)
inertia 系のイベントのみ発火した。
Inertia では Props データのみ更新されるため、コンポーネントの再構築は行われない。
その代わり裏で [vue] updated が呼ばれている。
ページ遷移もしていないので inertia:navigate は発火しない。
[document] inertia:before
[document] inertia:start
[document] inertia:success
[document] inertia:finish
▼ フォームを送信したとき(エラー)
成功時の success が error に変わっただけ。
もしエラーハンドリングが行いたい場合は inertia:error に差し込むと良い。
[document] inertia:before
[document] inertia:start
[document] inertia:error
[document] inertia:finish
▼ router.reload() を実行したとき
router.reload() は、自動的に preserveState: true となります。
そのため、発生するイベントも Props の更新だけになります。
[document] inertia:before
[document] inertia:start
[document] inertia:error
[document] inertia:finish
検証のため、 preserveState: false にしてみると...
router.visit(window.location.href, {
preserveState:false,
})
再マウントと通信の両方が発火しました。
しかし、同じURLだと inertia:navigate が発火しないのが興味深いです。
replace: true としても変わりませんでした。
[document] inertia:before
[document] inertia:start
[vue] beforeUnmount // コンポーネントの再構築
[vue] beforeCreate
[vue] beforeMount
[vue] unmounted
[vue] mounted
[document] inertia:success
[document] inertia:finish
おわりに
よく考えられてますねぇ...
何か特殊なことをしたい場合は、ぜひ参考にしてください!