Vue 3、Vuex 4 模块、Typescript

2025-06-07

Vue 3、Vuex 4 模块、Typescript

在本文中,我们将了解如何使用typescript以及Vue 3Vuex 4Vuex Modules

注意:Vuex 4 仍处于 Beta 版本。此外,随着全局变量this从 Vue 中移除,this.$storeVuex 4 中也移除了 store 的 type 属性,开发者需要自行编写。更多信息请参阅vuex-release-note

我们将创建像 root 和 counter 这样的模块,其中 counter 将是模块,root 将是 RootState。让我们在 store 文件夹内创建一个模块文件夹和一个将导出我们商店的 index.ts。
商店结构

我们还将拥有包含我们商店的所有接口的interfaces.ts文件。

import { ActionContext } from "vuex";
import { MutationTypes as CounterMTypes } from "./modules/counter/mutation-types";
import { ActionTypes as CounterATypes } from "./modules/counter/action-types";

export interface IRootState {
  root: boolean;
  version: string;
}

export interface CounterStateTypes {
  counter?: number;
  rootDispatch?: boolean
}

export interface CounterGettersTypes {
  doubledCounter(state: CounterStateTypes): number;
  counterValue(state: CounterStateTypes): number;
}

export type CounterMutationsTypes<S = CounterStateTypes> = {
  [CounterMTypes.SET_COUNTER](state: S, payload: number): void;
  [CounterMTypes.RESET_COUNTER](state: S): void;
};

export type AugmentedActionContext = {
  commit<K extends keyof CounterMutationsTypes>(
    key: K,
    payload: Parameters<CounterMutationsTypes[K]>[1]
  ): ReturnType<CounterMutationsTypes[K]>;
} & Omit<ActionContext<CounterStateTypes, IRootState>, "commit">;

export interface CounterActionsTypes {
  [CounterATypes.GET_COUNTER](
    { commit }: AugmentedActionContext,
    payload: number
  ): void;
}
Enter fullscreen mode Exit fullscreen mode

因此 CounterActionsTypes 充当计数器模块中操作的接口,CounterMutationsTypes 充当计数器模块中突变的接口,CounterGettersTypes 充当 getters 的接口。您可以从typescript-utility阅读有关 Omit 等实用程序的更多信息

让我们从创建计数器模块开始。

// store/modules/counter/state.ts
import { CounterStateTypes } from "./../../interfaces";

export const state: CounterStateTypes = {
  counter: 0,
};

// store/modules/counter/action-types.ts
export enum ActionTypes {
  GET_COUNTER = "GET_COUNTER"
}

// store/modules/counter/mutation-types.ts
export enum MutationTypes {
  SET_COUNTER = "SET_COUNTER",
  RESET_COUNTER = "RESET_COUNTER"
}

// store/modules/counter/getters.ts

import { GetterTree } from "vuex";
import {
  CounterGettersTypes,
  CounterStateTypes,
  IRootState
} from "./../../interfaces";

export const getters: GetterTree<CounterStateTypes, IRootState> &
  CounterGettersTypes = {
  counterValue: (state: CounterStateTypes) => {
    return state.counter || 0;
  },
  doubledCounter: (state: CounterStateTypes) => {
    return state.counter || 0 * 2;
  }
};

// store/modules/counter/mutations.ts
import { MutationTree } from "vuex";
import { MutationTypes } from "./mutation-types";
import { CounterMutationsTypes, CounterStateTypes } from "./../../interfaces";

export const mutations: MutationTree<CounterStateTypes> &
  CounterMutationsTypes = {
  [MutationTypes.SET_COUNTER](state: CounterStateTypes, payload: number) {
    state.counter = payload;
  },
  [MutationTypes.RESET_COUNTER](state: CounterStateTypes) {
    state.counter = 0;
  }
};

// store/modules/counter/actions.ts
import { ActionTree } from "vuex";
import { ActionTypes } from "./action-types";
import { MutationTypes } from "./mutation-types";
import {
  CounterActionsTypes,
  CounterStateTypes,
  IRootState
} from "@/store/interfaces";

export const actions: ActionTree<CounterStateTypes, IRootState> &
  CounterActionsTypes = {
  [ActionTypes.GET_COUNTER]({ commit }, payload: number) {
    commit(MutationTypes.SET_COUNTER, payload);
  }
};

// store/modules/counter/index.ts
import { Module } from "vuex";
import { CounterStateTypes, IRootState } from "@/store/interfaces";
import { getters } from "./getters";
import { actions } from "./actions";
import { mutations } from "./mutations";
import { state } from "./state";

// Module
const counter: Module<CounterStateTypes, IRootState> = {
  state,
  getters,
  mutations,
  actions
};

export default counter;
Enter fullscreen mode Exit fullscreen mode

现在我们已经创建了计数器模块,我们必须添加类型,让我们在计数器模块中创建 types.ts

// store/modules/counter/types.ts
import {
  CounterStateTypes,
  CounterMutationsTypes,
  CounterGettersTypes,
  CounterActionsTypes
} from "@/store/interfaces";
import { Store as VuexStore, CommitOptions, DispatchOptions } from "vuex";

export type CounterStoreModuleTypes<S = CounterStateTypes> = Omit<
  VuexStore<S>,
  "commit" | "getters" | "dispatch"
> & {
  commit<
    K extends keyof CounterMutationsTypes,
    P extends Parameters<CounterMutationsTypes[K]>[1]
  >(
    key: K,
    payload?: P,
    options?: CommitOptions
  ): ReturnType<CounterMutationsTypes[K]>;
} & {
  getters: {
    [K in keyof CounterGettersTypes]: ReturnType<CounterGettersTypes[K]>;
  };
} & {
  dispatch<K extends keyof CounterActionsTypes>(
    key: K,
    payload?: Parameters<CounterActionsTypes[K]>[1],
    options?: DispatchOptions
  ): ReturnType<CounterActionsTypes[K]>;
};
Enter fullscreen mode Exit fullscreen mode

因此,这将创建我们的计数器模块及其类型。现在让我们关注根,我们将在模块内创建一个根文件夹,它将用作 vuex 存储的根。
商店结构

这就是我们的文件夹结构之后的样子。
我们在根模块中唯一需要做的额外事情就是向其中添加模块,其余的都类似于计数器模块

// store/modules/root/index.ts

import { Module, ModuleTree } from "vuex";
import { IRootState } from "@/store/interfaces";
import { getters } from "./getters";
import { actions } from "./actions";
import { mutations } from "./mutations";
import { state } from "./state";
import counterModule from "../counter";

// Modules
const modules: ModuleTree<IRootState> = {
  counterModule,
};

const root: Module<IRootState, IRootState> = {
  state,
  getters,
  mutations,
  actions,
  modules
};

export default root;
Enter fullscreen mode Exit fullscreen mode

您可以看到我们已经将模块添加到根目录,并且可以将其传递给 createStore。

完成后,我们可以在 store 文件夹中设置 index.ts

import { createStore } from "vuex";
import { IRootState } from "@/store/interfaces";
import { CounterStoreModuleTypes } from "./modules/counter/types";
import { RootStoreModuleTypes } from "./modules/root/types";

import root from "./modules/root";

export const store = createStore<IRootState>(root);

type StoreModules = {
  counter: CounterStoreModuleTypes;
  root: RootStoreModuleTypes;
};

export type Store = CounterStoreModuleTypes<Pick<StoreModules, "counter">> &
  Counter1StoreModuleTypes<Pick<StoreModules, "counter1">> &
  RootStoreModuleTypes<Pick<StoreModules, "root">>;

Enter fullscreen mode Exit fullscreen mode

createStore<IRootState>(root);通过这种方式,我们将根模块标记为 rootState。Store将作为我们整个商店的类型。

我还在 store 文件夹中创建了 action-types.ts 和 mutation-types.ts,以便我们可以在一个地方完成所有操作和变异。

// store/action-types.ts
import { ActionTypes as counterTypes } from "./modules/counter/action-types";
import { ActionTypes as rootATypes } from "./modules/root/action-types";

export const AllActionTypes = { ...counterTypes, ...rootATypes };

// store/mutation-types.ts
import { MutationTypes as counterTypes } from "./modules/counter/mutation-types";
import { MutationTypes as rootMTypes } from "./modules/root/mutation-types";

export const AllMutationTypes = {...counterTypes,...rootMTypes };

Enter fullscreen mode Exit fullscreen mode

注意:由于我正在传播类型,如果出现相同的动作/变异类型,可能会覆盖。我们可以通过使用命名空间或完全避免这种情况来防止这种情况。

这样就完成了我们的商店,让我们看看如何在组件中使用我们的商店。
我们将在 src/use 文件夹中创建一个 useStore 实用程序。

import { Store } from "@/store";

import { useStore as VuexStore } from "vuex";
/**
 * Returns Whole Store Object
 */
export function useStore(): Store {
  return VuexStore() as Store;
}
Enter fullscreen mode Exit fullscreen mode

现在我们可以在视图和组件中直接导入 useStore。

类型支持

类型支持

通过这种方式,我们可以为商店提供类型支持,并且可以在商店中添加模块和类型。

注意:我跳过了创建商店的几个步骤,本文相关的所有代码都位于Repository中。
我之前阅读过的文章,之后才能够让模块在vuex-typescript中运行。

文章来源:https://dev.to/shubhadip/vue-3-vuex-4-modules-typescript-2i2o
PREV
您觉得我的 HTML5 游戏怎么样?
NEXT
什么是 GraphQL——误解。