Vuexにさわる

Vue3ではprovide/injectによる状態管理が推奨されているようなので使うつもりはなかったが、Global Before Guardsを使ったログイン状態によるページ制御をしようとした際にVueRouterでどう参照するのかわからなかったので使ってみる。

Vuex4になってTypeScriptへのサポートが強化されているらしい。

TypeScript サポート | Vuex

インストール

yarn add vuex@next --save

設定

src/store/AuthStore.ts

import { InjectionKey } from "vue";
import { createStore, useStore as baseUseStore, Store } from "vuex";

export interface IAccount {
  accountId: string;
  accountName: string;
}

export interface IState {
  account: IAccount | null;
  authToken: string | null;
}

export const key: InjectionKey<Store<IState>> = Symbol();

export const store = createStore<IState>({
  state: {
    account: null,
    authToken: null,
  },
  getters: {
    isLoggedIn: (state: IState): boolean => {
      return state.authToken ? true : false;
    },
    getAccount: (state: IState): IAccount => {
      return state.account ? state.account : ({} as IAccount);
    },
  },
  actions: {
    login({ commit, state }: any, payload: IState) {
      commit("setAccount", payload);
    },
    logout({ commit, state }: any) {
      commit("setAccount", { account: null, authToken: null });
    },
  },
  mutations: {
    setAccount(state: IState, { account, authToken }): void {
      state.account = account;
      state.authToken = authToken;
    },
  },
});

export function useStore() {
  return baseUseStore(key);
}

src/main.ts

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { store, key } from "./store/AuthStore";
import ElementPlus from "element-plus";
import "element-plus/lib/theme-chalk/index.css";

createApp(App).use(store, key).use(router).use(ElementPlus).mount("#app");

src/views/StoreSamplePage.vue

<template lang="pug">
MenuLayout
  el-row
    el-col(:span="10", :offset="7")
      p {{account.accountId}}
      p {{account.accountName}}
      p {{isLoggedIn}}
      el-button(type="primary", @click="login") login
      el-button(type="primary", @click="logout") logout
</template>

<script lang="ts">
import { defineComponent, computed, reactive } from "vue";
import MenuLayout from "@/layouts/MenuLayout.vue";
import { useStore, IState, IAccount } from "@/store/AuthStore";

export default defineComponent({
  name: "StoreSamplePage",
  components: {
    MenuLayout,
  },
  setup(props, context) {
    const store = useStore();
    return {
      account: computed(() => store.getters.getAccount),
      isLoggedIn: computed(() => store.getters.isLoggedIn),
      login: () =>
        store.dispatch("login", {
          account: {
            accountId: "U0000000001",
            accountName: "Jack",
          } as IAccount,
          authToken: "abcdefg1234567890",
        } as IState),
      logout: () => store.dispatch("logout"),
    };
  },
});
</script>