💡

ブラウザバックしたときにも初期化処理を呼びたい

に公開

はじめに

SPAで画面表示するさいに初期化処理を onMountedbeforeEnter に記述していたところ、ブラウザバックしたときにこれらの処理が再実行されないという問題に出会いました。

この記事では、<KeepAlive> コンポーネントと onActivated を使って解決する方法を紹介します。
記事内容のほかにも <KeepAlive> はpropsを持っていて特定のコンポーネントだけで onActivated を処理させることができそうです。
また、 beforeRouteUpdate でも「毎回初期化」は実現できそうです。

どんな問題が起きていたか

ユーザーの詳細情報を表示するページ /users/:id のような画面を例に考えてみましょう。
このページにアクセスした際に、ユーザーIDを使ってAPIから詳細情報を取得し、画面に表示する、という実装はよくあります。

当初 onMounted を使ってコンポーネントのマウント時にデータを取得する処理を記述していました。

components/UserDetail.vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { fetchUserById } from '@/api/users' // APIを叩く関数

const route = useRoute()
const user = ref(null)

onMounted(async () => {
  console.log('onMounted hook called!') // デバッグ用のログ
  const userId = route.params.id as string
  user.value = await fetchUserById(userId)
})
</script>

初めてアクセスするときは問題ありませんでしたが、さらに別のページへ移動してからブラウザバックするとonMountedが発火しないようでした。

解決策: <KeepAlive> と onActivated

解消のため、<KeepAlive>onActivated ライフサイクルフックを採用しました。

  1. <router-view><KeepAlive> で囲む
    まず、コンポーネントの状態を保持するために、 <router-view><KeepAlive> タグでラップします。
<template>
  <router-view v-slot="{ Component }">
    <keep-alive>
      <component :is="Component" />
    </keep-alive>
  </router-view>
</template>

<KeepAlive> でラップされたコンポーネントは、非アクティブになってもインスタンスが破棄されずにメモリ内に保持(キャッシュ)されます。

  1. onMountedonActivated に置き換える
    次に、初期化処理を記述するフックを onMounted から onActivated に変更します。

onActivated フックは、<KeepAlive> によってキャッシュされたコンポーネントが、再びアクティブになった(表示された)タイミングで毎回呼び出されます。ブラウザバックで戻ってきた場合も、このフックが実行されます。

先ほどのコンポーネントを以下のように修正します。

components/UserDetail.vue
<script setup lang="ts">
import { ref, onActivated } from 'vue'
import { useRoute } from 'vue-router'
import { fetchUserById } from '@/api/users'

const route = useRoute()
const user = ref(null)

// onMounted の代わりに onActivated を使用
onActivated(async () => {
  console.log('onActivated hook called!')
  const userId = route.params.id as string
  user.value = await fetchUserById(userId)
})
</script>

これで、ブラウザバックでもこのページを表示するたびに onActivated が実行され、毎回初期化処理が呼ばれるようになりました。
ページを離れるときにタイマーやイベントリスナを消したいときは onDeactivated を利用することもできるようです。

まとめ

画面が表示されるたびに呼びたい処理があるとき、<KeepAlive>onActivated を組み合わせることで実現できるようでした。
逆に、onMountedbeforeEnter だと初回だけ呼ばれてしまい、その後は実行されないことがあります。

Discussion