【Vuex, Nuxt.js】子コンポーネントのアクションを実行しても状態が反映されない

最近Nuxt.jsを使ってサービスの開発をしてみてます。

フロントエンドのフレームワークの経験がなく慣れておらず、

状態管理でハマったところがあったので備忘録として投稿。

個人的にはてなブログにかいてましたが
技術系はQiitaに書こうと決めたので内容は下記の書き直しとなります。
https://moritomo7315.hatenablog.com/entry/vuex/state_manage/1

ちなみにログイン認証等は今回の記事の本質とは離れてるので割愛してます。


## 何にハマったか

userのログイン・ログアウトをVuexで

```javascript
// login
loginStatus = true
// logout
loginStatus = false
```

のように状態管理しようとしたわけですが、下記のような現象が起きてしまいました。

* ログアウトする場合：loginStatusがtrue->falseに変化
* ログインする場合：loginStatusがfalseのまま


## ソースコード

### store

```javascript:index.js
import Vuex from 'vuex'
import user from './modules/user'

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

```javascript:user.js
const state = {
  user: null,
  loginStatus: false
}

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 コンポーネント
```javascript:header.vue
<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
            @click="logout"
            nuxt
            to='/'
            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"
    ])
  }
}
</script>
```
### login page

```javascript:login.vue
<!-- EmailSigninコンポーネントを呼び出してるだけ。今後Twitterログイン等も入れたいので、コンポーネントに分けている -->
<template>
  <EmailSignin />
</template>

<script>
import EmailSignin from '~/components/emailSignin'
export default {
  components: {
    EmailSignin
  }
}
</script>
```

```javascript:emailSignin.vue
<template>
  <div class="userpage">
    <h1>ログイン</h1>
    <div class="userform">
      <v-form>
        <v-text-field
          v-model="email"
          label="E-mail"
          outlined
          required
        ></v-text-field>

        <v-text-field
          v-model="password"
          label="password"
          outlined
          type="password"
          required
        ></v-text-field>
        <v-btn
          color="secondary"
          class="mr-4"
          @click="login"
          x-large
          nuxt
          to="/"
        >
          ログイン
        </v-btn>
        <nuxt-link
          to="#"
        >
          パスワードを忘れた方はこちら...
        </nuxt-link>
      </v-form>
    </div>
  </div>
</template>

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

export default {
  name: "EmailSignin",
  data() {
    return {
      email: '',
      password: '',
    }
  },
  computed: {
    ...mapState({
      user: state => state.user.user,
      loginStatus: state => state.user.loginStatus
    })
  },
  methods: {
    ...mapActions('user',[
      "login"
    ]),
    passworLogin () {
      this.login()
    }
  }
}
</script>
```

## 何が問題だったか

結論から言うと、

**`@click`の使い方**が原因でした。

ソースコードでいうと、storeの記述に関しては問題なしです。

### なぜログアウトはできて、ログインができなかったか
なぜログアウトはできるのに、
ログインはできないといった現象が起きてしまっていたかと言うと、

これは親子コンポーネントとイベントの関係があるみたいです。


僕のソースコードで実装されてるログアウトとログインの違いは

* headerコンポーネントがコンポーネント内のアクション`logout`を呼びイベント発生
* loginコンポーネントがemailSigninコンポーネント内の`login`を呼びイベント発生

となっております。


どちらも`@click="methodName`としていますが、

ログインだけ動かないのはコンポーネントの親子関係が原因でした。

親コンポーネントに子コンポーネントのアクションイベントを実行させるには、

`@click.native="methodName`

と指定する必要があるみたいです。

## まとめ

コンポーネントの親子関係を意識して、

そのコンポーネントが親か子に応じて

`@click`か`@click.native`を指定するで解決しました。

まだvuexの概念を理解しきっていないので、

説明に使用してる言葉があやしいところもあるかもしれませんが、

無事解決できました。