vuex中ts类型约束

355 阅读4分钟

以前写的项目在vuex方面都是没有做类型约束的,用的是秘籍 “any大法好”,以至于ts形同虚设;再这样下去,技术还了得,得改变现状。月初,得知即将要开启一个新项目,我决定,代码和人都不能这么颓废了,必须得看看如何规范store的类型。话不多说,马上开始:

首先,当然借鉴文档和百家之智慧

vuex官方文档

因为使用的vue3,就直接看这个了;

百家之智慧因为参考太多,没有mark下,就没有链接啦;

参考完所有资料,现在先从简单的开始吧!!

基本结构了解

// store/index.ts

import { createStore } from 'vuex'

export default createStore({
  state: {},
  getters: {},
  mutations: {},
  actions: {},
  modules: {},
})
// main.ts

import store from '@/store'

app.use(store)

类型声明步骤

关键词InjectionKey

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

以上步骤来自 vuex 官网

设置 store 的根模块 state 数据类型接口

暂不考虑子模块的情况

// 一般接口统一写在 interface 文件中,这里直接在 store/index 文件中
// store/index.ts

interface IRootState {
    count: number
}

import { createStore } from 'vuex'

export default createStore<IRootState>({
  state: {
      count: 0, // 必须有 count 属性,不然会报错
  },
})

// 根模块的 state 就会受到 IRootState 的规范

定义一个唯一的 key 值

使用 Vue 的 InjectionKey 接口和自己的 store 类型定义(步骤第二点)

// index.js

import { InjectionKey } from 'vue'
import { createStore, Store } from 'vue-router'

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

// 将 store 安装到 Vue 应用时提供类型化的 `InjectionKey` 。
// InjectionKey<T>    T => Store
// Store<S>           S => IRootState

InjectionKey的使用和含义还是很不理解,但还是先照着写下去,后续了解清楚明白会补充

将 key 值传给 useStore 方法

在 main.ts 文件挂载 store 时,也需要把 key 值传进去

// main.ts

import store, { key } from '@/store'

app.use(store, key);

实现方法1

在每次使用 store 时,useStore 方法要传入 key 值

// app.vue

import { useStore } from 'vuex'

const store = useStore(key);

const count = computed(() => store.state.count);
{{ count }}

实现方法2

创建一个新的 useStore 函数替换原本 useStore ,传递 key 为参数并将函数返回出去;

以后使用时就是使用新返回的 useStore 函数

// store/index.ts

import { useStore as basUseStore } from 'vue-router'
// 使用别名是因为要 export 的函数名仍然是 useStore
// 但其执行内容与原来的 useStore 不同,所做的一个改变

export function useStore() {
    return baseUseStore(key); // key 传入
}

// app.vue

import { useStore } from '@/store' // 与方法1不同

const store = useStore(); // 与方法1不同

const count = computed(() => store.state.count);
{{ count }}

经上述方法已经成功了一半了,放松放松

放松完后,现在继续处理 module 相关的内容

设置子模块的 state 类型接口

// store/modules/common.ts

interface ICommonStore {
    age: number
}

设置模块的类型约束

关键词:Module

image.png

第一个是当前模块的类型,第二个是根模块的类型

import { Module } from 'vuex'

const common:Module<ICommonStore, IRootState> = {
  namespaced: true,
  state: {
    age: 4
  },
};

子模块新建完成后,在根模块的 modules 中引入

使用 common 模块的 age

const age = computed(() => store.state.common.age);
// 会出现如下错误提示

image.png 问题不大,是因为 IRootState 中没有定义 common

完善 IRootState 的约束


export default createStore<IRootState>({ // IRootState1
  state: {
    count: 0,
  },
  modules: {
    common,
  }
});

export const key:InjectionKey<Store<IRootState>> = Symbol(); // IRootState2

从代码可知, IRootState 只用在两个地方;(因为不确定是哪个,所以两个试着修改了)

  1. 尝试修改 IRootState1 ,把 common 也定义在 IRootState 接口中
interface IRootState {
    count: number,
    common: ICommonStore
}

image.png 根模块中的 state 就会提示错误,因为上述写法 相当于在 根state 中需要有这个属性,但这个属性其实是 modules 的,所以不是更改 IRootState1

  1. 修改 IRootState2,思考一下,这是针对 store 的类型,因此要继承 根模块 的类型也要新增 子模块类型
// 定义一个全部类型的接口

interface IAllOptions extends IRootState {
    common: ICommonStore,
}

export const key:InjectionKey<Store<IAllOptions>> = Symbol(); // 作出修改

至此,store 就有了正确的类型,也能正常使用了。

所有代码

  • store/interface.ts
export interface IRootState {
  count: number,
}

export interface ICommonStore {
  age: number,
}

export interface IAllOption extends IRootState {
  common: ICommonStore,
}
  • store/index.ts
import { createStore, useStore as baseUseStore, Store } from 'vuex'
import { InjectionKey } from 'vue'

import { IAllOption, IRootState } from './interface'

import common from './modules/common';

export default createStore<IAllOption>({
  state: {
    count: 0,
  },
  mutations: {
    ADD_COUNT(state, data) {
      state.count += data;
    }
  },
  modules: {
    common,
  }
});

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

export function useStore() {
  return baseUseStore(key);
}
  • store/modules/common.ts
import { Module } from 'vuex';
import { IRootState, ICommonStore } from '../interface';

const common:Module<ICommonStore, IRootState> = {
  namespaced: true,
  state: {
    age: 4
  },
  mutations: {
    ADD_AGE: (state, data) => {
      state.age = state.age + data;
    }
  }
};

export default common;

结语

其实,这里感觉有很多原理层面上的东西还是没有弄明白,只是凑合着能用,后面就得好好的深剖了,如有错误,欢迎指正和补充,谢谢。