見出し画像

Nuxt.js 状態管理でハマった件

(Nuxt.js v2.4.2)
ノリでNuxt.js使ったらえらい目にあったので共有します。1日潰しました。対象読者はまぁ自分がWEBの経験が浅い(現時点で8ヶ月程度)なので初心者レベルの内容です。

tl;dr

 - window.location や href でページ遷移すると、Storeに格納にした状態が飛ぶ。理由としてはページが初回ロード扱いとなるため。nuxtServerInitアクションで状態を復元するコードを書かなくてはならない。
 - NuxtLink や this.$router.pushを使用してページ遷移すると、ページが初期化されないため状態が維持される。

Nuxt.js とは

いろんな人が書いている。自分の言葉でまとめるなら、Vueを使ったWEBフレームワーク。

認証ページ を作るには

ログインしてたら、この画面が表示されて、そうでなければリダイレクト or エラーページに遷移する的なことをやりたい。そこで Storeを使用する。Nuxt.jsが梱包するVuexの機能に「Store」が含まれている。Storeは画面間でデータを共有できるグローバルデータみたいなもの。これを使って、認証ページの機能が作れる。公式ページ 認証ルート - Nuxt.jsで、まさにサンプルが用意されておりこれを元に読み進めるのがいいけど、まずはそのページにあるソースコード本体をダウンロードしてほしい。ソースとドキュメントの内容が乖離してるし、ドキュメントは一部にフォーカスしてるのであまりあてにならない。雰囲気を理解する程度に留めた方がいいように思う。この記事でもそのソースコードを元にハマりポイントを吐露したい。

本題; Storeに格納されたデータが飛ぶ

ログイン後、適当なページに遷移させたいときにこんなコードを書くと思う。ダウンロードしたソースコードを以下のように修正します...

pages/index.vue

〜中略〜
<script>
 methods: {
   async login() {
     try {
       await this.$store.dispatch('login', {
         username: this.formUsername,
         password: this.formPassword
       })
       this.formUsername = ''
       this.formPassword = ''
       this.formError = null
       window.location = '/secure'; // 🚨追加したコード
     } catch (e) {
       this.formError = e.message
     }
   },
〜以下略〜

このコードで/secureページへ遷移しようとすると、store/index.jsに定義したauthUserというstateが破棄され、認証に失敗する。secure.vueへのページ遷移ができない。なぜ?

Storeを調査してみる

// store/index.js

export const state = () => ({
    authUser: null
})

保持したいstateの定義。特に深掘りする理由なし。

// store/index.js

export const mutations = {
 SET_USER: function (state, user) {
   state.authUser = user
 }
}

stateを更新する部分。公式ページによると、このmutationsにstate更新処理を登録しておく必要がある。それ以外の場所では、更新できない。

// store/index.js

export const actions = {
 // nuxtServerInit is called by Nuxt.js before server-rendering every page
 nuxtServerInit({ commit }, { req }) {
     if (req.session && req.session.authUser) {
        commit('SET_USER', req.session.authUser)
     }
 },
 async login({ commit }, { username, password }) {
   try {
     const { data } = await axios.post('/api/login', { username, password })
     commit('SET_USER', data)
   } catch (error) {
     if (error.response && error.response.status === 401) {
       throw new Error('Bad credentials')
     }
     throw error
   }
 },

 async logout({ commit }) {
   await axios.post('/api/logout')
   commit('SET_USER', null)
 }

}

login/logoutメソッドは自前で定義された関数だとしてnuxtServerInitというメソッドはなんだろう。VuexではなくNuxt.jsのコールバックらしい。でも、そんなライフサイクル見つからないと思ったらここに良い記事があった。図を抜粋する。

画像1

Webアプリを作るとき、初回に本体(というのが適切?)一式をダウンロードする。この時、nuxtServerInitがコールバックされる。公式ページのソースコード上ではセッションデータからstate情報を復元しようとしていることがわかる。つまり、nuxtServerInitはその名の通り初期化なので、stateを復元をしようとしなければそれが初期化されたままということになる。SPAでない場合、このイベントはページ遷移毎にコールされる・・・。と思ったら、このNavigateの部分を見てほしい。これはnuxt-linkを使ったページ遷移を意味している。つまり、nuxt-linkを使うとstateが保持される。

nuxt-link と state

index.vueに戻ると確かにログイン後に遷移可能なページ遷移をする場合、nuxt-linkが使用されている。NuxtLinkはnuxt-linkの別名だ。

// pages/index.vue

<template>
 <div class="container">
〜中略〜
   <p>
     <NuxtLink to="/secret">
       Super secret page
     </NuxtLink>
   </p>
 </div>
</template>
〜以下略〜

試しにこれをhrefに変えてやると遷移に失敗する。

<!-- NuxtLink to="/secret" -->
<a href="/secret">

このNuxtLinkをJavascriptで書くには以下のようになる。

this.$router.push('/secure')

全体としてはこんな感じ

// pages/index.vue

〜中略〜
<script>
 methods: {
   async login() {
     try {
       await this.$store.dispatch('login', {
         username: this.formUsername,
         password: this.formPassword
       })
       this.formUsername = ''
       this.formPassword = ''
       this.formError = null
       // window.location = '/secure'; 
       this.$router.push('/secure'); // 💖修正したコード
     } catch (e) {
       this.formError = e.message
     }
   },
〜以下略〜

リンクにせよNuxtLinkにせよ、ページを離れてから戻ってきたときに状態を維持したいならnuxtServerInitで状態を復元する必要があるけど...。

以上です。


この記事が気に入ったらサポートをしてみませんか?