Vue3系列:修复Vuex的类型推断

1,586 阅读4分钟

Vue3.0 系列之 Vuex

通过本系列 可带你初步体验Vue3.0及其生态 🚀

系列文章:

前言

🤔 大致查了下网上的基本都是JS下使用Vuex,本文将完善Vue3.0的Ts下Vuex对应的State和Model的类型推断~😎

项目中引入Vuex


1. 通过脚手架安装
 vue-cli 创建项目时通过勾选默认配置生成Vuex
  1. npm安装
 npm install vuex --save
  1. 直接下载 / CDN引用
 https://unpkg.com/vuex@4.0.0/dist/vuex.global.js 这样的方式指定特定的版本

以上废话是常规操作下面正式步入主题。

Vuex正常使用

在vue-cli 脚手架中生成的store中默认是这样的:

// store/index.ts
import { createStore } from "vuex";

export default createStore({
  state: {},
  mutations: {},
  actions: {},
  modules: {},
});

简单模拟项目中使用情况

// store/index.ts
import { createStore } from "vuex";

export default createStore({
  state: {
    name: "gby",
    isVip: true,
    hobby: ["足球", "看电影", "其他"],
  },
  mutations: {
   // state不能直接更改, 在这里修改值
  },
  actions: {
      // 业务操作...
  },
  modules: {
      // user模块
      // login模块
      // ....
  },
});

Home.vue中实际使用:

动画.gif

发现vue文件调用下state没有nameisVip等属性的快捷提示更没有类型推断。

第一次封装

在官网中查看到 useStore 组合式函数类型声明,官网上是这么说的:

当使用组合式 API 编写 Vue 组件时,您可能希望 useStore 返回类型化的 store。为了 useStore 能正确返回类型化的 store,必须执行以下步骤:

  1. 定义类型化的 InjectionKey
  2. 将 store 安装到 Vue 应用时提供类型化的 InjectionKey 。
  3. 将类型化的 InjectionKey 传给 useStore 方法。

下面按照官网示例代码具体实现如下。

// store/index.ts
import { InjectionKey } from "vue";  // 定义类型化的 `InjectionKey`
import { createStore, Store } from "vuex";

// 为 store state 声明类型
export interface State {
  name: string;
  isVip: boolean;
  hobby: string[];
}
// 1. 定义 injection key
export const key: InjectionKey<Store<State>> = Symbol();

export default createStore<State>({
  state: {
    name: "gby",
    isVip: true,
    hobby: ["足球", "看电影", "其他"],
  },
  ...
});
// main.ts
...
import { createApp } from 'vue'
import { store, key } from  "./store/index"

const app = createApp(App)
// 2.  将 store 安装到 Vue 应用时提供类型化的 `InjectionKey` 。
app.use(store, key)
...
app.mount("#app");

Home.vue中使用需要将将类型化的 InjectionKey 传给 useStore 方法。:

// vue 组件
import { useStore } from "vuex";
import { key } from "@/store/index";

export default defineComponent({
  ...
  setup () {
    // 3.  将类型化的 `InjectionKey` 传给 `useStore` 方法。
    const store = useStore(key)
    retrun {}
  }
})

实际使用效果如下:

Home2.gif

从效果中可以看出来目前会对state有属性提示了并且在我給一个类型为Stringname赋值false会有错误提示啦。

第二次封装

以上虽然state中有类型推断了,但有有两个问题出现了:

  1. 每次在vue组件中使用的时候都需要引入store下定义的key(不方便)
  2. 一般都加在modules里的state呢 也会有同样的提示么 ?

针对第一点:(每次在vue组件中使用不方便) 优化如下👇

// store/index.ts

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

// 定义自己的 `useStore` 组合式函数
export function useStore(): Store<State> {
  return baseUseStore(key);
}

这样每次在vue组件中使用就舒服了一点点

// import { useStore } from "vuex"; // 优化前
// import { key } from "@/store/index"; // 优化前
import { useStore } from "@/store/index"; // 优化后

export default defineComponent({
  setup() {
      // const store = useStore(key); // 优化前
      const store = useStore(); // 优化后
  }
})

现在我进一步添加一个模块,尝试一下在modules里的state也会有提示么。

// store/login/login.ts
import { State as RootSate } from "@/store/index";
import { Module } from "vuex";

export interface ILoginSate {
  token: string; // token
  lastLoginTime: string; // 上次登录时间
  role: "VIP" | "SVIP" | "developer"; // 用户权限
}

// Module<S, R> => S 是当前模块state接口、R是全局定义的Store下的state接口
const loginModel: Module<ILoginSate, RootSate> = {
  namespaced: true,
  state: () => {
    return {
      token: "",
      lastLoginTime: "",
      role: "developer",
    };
  },
  ...
};
export default loginModel;
// store/index.ts
...
import loginModel from "./login/login";
...
export default createStore<State>({
  ...
  modules: {
    // login模块
    login: loginModel,
  },
});

vue组件中调用发现是不会提示login及其模块下的state。(这个不是gif图)

image.png

这时感觉到应该是和key传入得约束有关,从次入口进一步封装如下:

// store/index.ts
...
import loginModel, { ILoginSate } from "./login/login"; // 将ILoginSate调用过来
// 新增
export interface State {
  name: string;
  isVip: boolean;
  hobby: string[];
}
interface IModulesState {
  login: ILoginSate;
  // 其他模块
}
type IStoreType = State & IModulesState; // 合并类型

// 重新约束key
// export const key: InjectionKey<Store<State>> = Symbol();
export const key: InjectionKey<Store<IStoreType>> = Symbol();

// 重新约束useStore
// export function useStore(): Store<State> {
export function useStore(): Store<IStoreType> {
...

最终的运行效果

Home3.gif

可看到有很好的提示statemodules和类型推断了😄,到这里就封装完成啦。

这上面的对于key的约束是可以单独提出来到一个type.ts文件中使我们的index.ts更加清晰、更加好维护。

按照这个模式在项目中实际使用起来吧。

【还有一种方法不采用 InjectionKey】

留坑(会新开一篇来写, 写好了会贴链接的)

结尾