MORITOMOMENT

登山好きエンジニアのテックブログ

プログラミング・アウトドア関連を中心に発信

Nuxt.jsでMaximum call stack size exceededというエラーに悩まされた

f:id:moritomo7315:20200422224527p:plain

最近、nuxt.jsを使って個人サービス開発に励んでおります。

がしかし、

Javascriptをほとんど触ってこなかった人なのでこのエラーには大変苦戦しました。

その解決に至った経路だとか方法を備忘録として書き留めます。

どんな風にエラーが起きたか

エラーが生じたときの状態は

ヘッダーにあるv-list-itemから@clickでアクションを実行しログアウト。

トップページ的なところに遷移するはずがエラー画面に。

developer toolをみると、こんなエラーが

RangeError: Maximum call stack size exceeded
    at VueComponent.logout (header.vue?24d4:71)
    at VueComponent.logout (header.vue?24d4:72)
    at VueComponent.logout (header.vue?24d4:72)
    at VueComponent.logout (header.vue?24d4:72)
    at VueComponent.logout (header.vue?24d4:72)
    at VueComponent.logout (header.vue?24d4:72)
    at VueComponent.logout (header.vue?24d4:72)
    at VueComponent.logout (header.vue?24d4:72)
    at VueComponent.logout (header.vue?24d4:72)
    at VueComponent.logout (header.vue?24d4:72)

エラーからして、logoutが何回も実行されてるようにみえる。。

つまり何らかの理由で

無限ループしていてスタックサイズ超えちゃったよ、あーあ、

ってわけですね。

エラーが起きたソースコード

ソースコードの説明は省略してます。

index.js

// storeを定義してるモジュールを読み込むファイル
import Vuex from 'vuex'
import user from './modules/user'
import posts from './modules/posts'

export default () => new Vuex.Store({
  modules: {
    user,
    posts
  }
})

user.js

// userに関するstoreを定義してるモジュール
const state = {
  user: null,
  loginStatus: true // ログイン状態を擬似的に表現してたので最初からtrue
}

const getters = {
  user: (state) => state.user,
  isLogin: (state) => state.loginStatus
}

const mutations = {
  setUser(state, { user }) {
    state.user = user
  },
  login(state) {
    state.loginStatus = true
  },
  logout(state) {
    state.loginStatus = false
    state.user = null
  }
}

const actions = {
  fetchUser({ commit }, user) {
    commit('setUser', {user})
  },
  login({ commit }) {
    commit('login')
  },
  logout({ commit }) {
    commit('logout')
  }
}

export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations
}

Header.vue

<!-- Header component 全くAtomic Design意識してないから参考にしないでください。 -->
<template>
  <div>
    <v-app-bar
      color="primary"
    >
      <v-toolbar-title>
        <nuxt-link to='/'>
          <img src="~/static/weblogo.png">
        </nuxt-link>
      </v-toolbar-title>
      <v-spacer></v-spacer>
      <v-menu offset-y>
        <template v-slot:activator="{ on }">
          <v-btn
            icon
            color="transparent"
            v-on="on"
          >
            <v-app-bar-nav-icon></v-app-bar-nav-icon>
          </v-btn>
        </template>
        <v-list v-if="loginStatus">
          <v-list-item
            nuxt
            to='#'
          >
            <v-list-item-title>マイページ</v-list-item-title>
          </v-list-item>
          <v-list-item
              nuxt
              to='/'
              @click="logout"
              inactive
          >
            <v-list-item-title>ログアウト</v-list-item-title>
          </v-list-item>
        </v-list>
        <v-list v-else>
          <v-list-item
            nuxt
            to='/login'
          >
            <v-list-item-title>ログイン</v-list-item-title>
          </v-list-item>
          <v-list-item
            nuxt
            to='/signup'
          >
            <v-list-item-title>会員登録</v-list-item-title>
          </v-list-item>
        </v-list>
      </v-menu>
    </v-app-bar>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions } from "vuex";

export default {
  name: "Header",
  computed: {
    ...mapState({
      user: state => state.user.user,
      loginStatus: state => state.user.loginStatus
    })
  },
  methods: {
    ...mapActions('user',[
      "logout"
    ]),
   logout: function() {
      this.logout()
    }
  }
}
</script>

バックエンドばかり普段触ってる身からすると、

無限ループになりそうなところはないので非常に原因が何か分かりづらかったです。

(解決後には「確かに無限ループするじゃん!」ってなりましたw)

解決策

で、色々ググってたらこのサイトのおかげで解決ができました。

forum.vuejs.org

あ、確かに〜、と腑に落ちた原因でした。

何がいけなかったのかというと、

methods: {
    ...mapActions('user',[
      "logout" // user storeで定義されてるmutation実行のためのアクション
    ]),
   logout: function() { //コンポーネントからログアウトを発火させるためのメソッド
      this.logout() // user storeで定義されてるmutation実行のためのアクション
    }

このような意図でコンポーネント内でStoreのアクションを実行するメソッドを定義していましたが、

実際this.logout()コンポーネント内のlogoutメソッドと解釈されてしまうようでした。

要するにlogout()が自分自身を呼び続けていたために起きてしまったエラーでした。

そりゃ無限ループになりますよね。

実際のところ、

...mapActions('user',[
      "logout"
    ])

この宣言だけで、

コンポーネント内でStoreで定義してるアクションは使用できるようになってるみたいなので、

Header.vueのJavascriptの部分を下記のように直すことで解決でした。

<script>
import { mapState, mapGetters, mapActions } from "vuex";

export default {
  name: "Header",
  computed: {
    ...mapState({
      user: state => state.user.user,
      loginStatus: state => state.user.loginStatus
    })
  },
  methods: {
    ...mapActions('user',[
      "logout"
    ])
  }
}
</script>