以前写的项目在vuex方面都是没有做类型约束的,用的是秘籍 “any大法好”,以至于ts形同虚设;再这样下去,技术还了得,得改变现状。月初,得知即将要开启一个新项目,我决定,代码和人都不能这么颓废了,必须得看看如何规范store的类型。话不多说,马上开始:
首先,当然借鉴文档和百家之智慧
因为使用的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
- 定义类型化的
InjectionKey
。- 将 store 安装到 Vue 应用时提供类型化的
InjectionKey
。- 将类型化的
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
第一个是当前模块的类型,第二个是根模块的类型
import { Module } from 'vuex'
const common:Module<ICommonStore, IRootState> = {
namespaced: true,
state: {
age: 4
},
};
子模块新建完成后,在根模块的 modules 中引入
使用 common 模块的 age
const age = computed(() => store.state.common.age);
// 会出现如下错误提示
问题不大,是因为 IRootState 中没有定义 common
完善 IRootState 的约束
export default createStore<IRootState>({ // IRootState1
state: {
count: 0,
},
modules: {
common,
}
});
export const key:InjectionKey<Store<IRootState>> = Symbol(); // IRootState2
从代码可知, IRootState 只用在两个地方;(因为不确定是哪个,所以两个试着修改了)
- 尝试修改 IRootState1 ,把 common 也定义在 IRootState 接口中
interface IRootState {
count: number,
common: ICommonStore
}
根模块中的 state 就会提示错误,因为上述写法 相当于在 根state 中需要有这个属性,但这个属性其实是 modules 的,所以不是更改 IRootState1
- 修改 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;
结语
其实,这里感觉有很多原理层面上的东西还是没有弄明白,只是凑合着能用,后面就得好好的深剖了,如有错误,欢迎指正和补充,谢谢。