ts改造vuex全链路指南

ts发展到今天,目前主流的前端技术栈都在向其靠齐。20年我们用vue2.x+ts+vuex3.x+electron开发了一款即时聊天客户端,那时候感受到了ts强大的魅力。然而因为当时项目赶工期,对于整个项目的vuex板块并没有做很好的实践优化。对于我这样不断追求最佳实践的开发者来说是个遗憾。时隔一年,我终于可以着手将其改造,让我的团队享受我带给他们的最佳开发实践成果,同时我愿意把改造思路全盘分享给大家。

目标:实现state\module state\getter\module getter\mutation\module mutation\action\module action智能提示和类型约束。

一、实现state提示:

文件创建目录


image.png

type.ts中创建代码

export interface RootStateType {
  name: string
}

index.ts中创建代码

import { Store } from 'vuex'
import { RootStateType } from './type'

const store = new Store<RootStateType>({
  state: {},
})

此刻,你应该会得到一个错误


image.png

index.ts中输入name属性

import { Store } from 'vuex'
import { RootStateType } from './type'

const store = new Store<RootStateType>({
  state: {
    name : 'test'
  },
})

此时引入store到入口文件,得到的store类型如下


image.png

直接调用store的state属性,出现提示


image.png

在vuex目录中新建模块文件夹router
image.png

同样的方式约束router(type.ts)

export interface RouterStateType {
  path: string
}

实现router(router.ts)

import { Module } from 'vuex'
import { RootStateType } from '../type'
import { RouterStateType } from './type'

const router: Module<RouterStateType, RootStateType> = {
  state: {
    path: 'path',
  },
}

export default router

改造vuex(index.ts)类型约束

import { RouterStateType } from './router/type'

export interface RootStateType {
  name: string
  router: RouterStateType
}

vuex(index.ts)报错


image.png

继续改造vuex(index.ts)类型约束,报错消失

import { RouterStateType } from './router/type'

export interface RootStateType {
  name: string
  router?: RouterStateType
}

store使用


image.png

截止如此,state及module state已经实现调用提示

二、 实现getter提示
在vuex里建getters.ts

import { GetterTree } from 'vuex'
import { RootStateType } from './type'

export interface Getters {
  getName(state: RootStateType): string
}
export const getters: GetterTree<RootStateType, RootStateType> & Getters = {
  getName(state) {
    return state.name
  },
}

在vuex的index.ts中接入getters

import { Store } from 'vuex'
import { getters } from './getters'
import { RootStateType } from './type'

const store = new Store<RootStateType>({
  state: {
    name: 'test',
  },
  getters: getters,
})

export default store

此时调用store.getters提示是any类型


image.png

追踪定义npm中的vuex定义


image.png

改造vuex中的index.ts
import { Store } from 'vuex'
import { Getters, getters } from './getters'
import { RootStateType } from './type'

const store = new Store<RootStateType>({
  state: {
    name: 'test',
  },
  getters: getters,
})

export default store as Omit<Store<RootStateType>, 'getters'> & {
  getters: {
    [K in keyof Getters]: ReturnType<Getters[K]>
  }
}

再次调用store.getters


image.png

补充完全vuex/router/router.ts

import { Module } from 'vuex'
import { RootStateType } from '../type'
import { RouterStateType } from './type'

const routerStore: Module<RouterStateType, RootStateType> = {
  namespaced : true,
  state: {
    path: 'path',
  },
}

export default routerStore

补充vuex/index.ts

import { Store } from 'vuex'
import { Getters, getters } from './getters'
import routerStore from './router/router'
import { RootStateType } from './type'

const store = new Store<RootStateType>({
  modules: {
    router: routerStore,
  },
  state: {
    name: 'test',
  },
  getters: getters,
})

export default store as Omit<Store<RootStateType>, 'getters'> & {
  getters: {
    [K in keyof Getters]: ReturnType<Getters[K]>
  }
}

添加vuex/router/getters.ts

import { GetterTree } from 'vuex'
import { RootStateType } from '../type'
import { RouterStateType } from './type'

export interface RouterGetters {
  getPath(state: RouterStateType): string
}
export const routerGetters: GetterTree<RouterStateType, RootStateType> &
  RouterGetters = {
  getPath(state) {
    return state.path
  },
}

改造vuex/index.ts

import { Store } from 'vuex'
import { Getters, getters } from './getters'
import { RouterGetters, routerGetters } from './router/getters'
import routerStore from './router/router'
import { RootStateType } from './type'

const store = new Store<RootStateType>({
 modules: {
   router: routerStore,
 },
 state: {
   name: 'test',
 },
 getters: getters,
})

export default store as Omit<Store<RootStateType>, 'getters'> & {
 getters: {
   [K in keyof Getters]: ReturnType<Getters[K]>
 } & {
   [K in keyof RouterGetters]: ReturnType<RouterGetters[K]>
 }
}

store调用


image.png

好像不太对,继续改造vuex/index.ts

import { Store } from 'vuex'
import { Getters, getters } from './getters'
import { RouterGetters, routerGetters } from './router/getters'
import routerStore from './router/router'
import { RootStateType } from './type'

const store = new Store<RootStateType>({
  modules: {
    router: routerStore,
  },
  state: {
    name: 'test',
  },
  getters: getters,
})

export default store as Omit<Store<RootStateType>, 'getters'> & {
  getters: {
    [K in keyof Getters]: ReturnType<Getters[K]>
  } & {
    [K in keyof RouterGetters as `router/${K}`]: ReturnType<RouterGetters[K]>
  }
}

继续调用


image.png

到目前为止getter的改造也完成了

三、改造mutation
在vuex目录中创建mutations.ts

import { MutationTree } from 'vuex'
import { RootStateType } from './type'

export interface Mutations {
  SET_NAME(state: RootStateType, payload: string): void
}

export const mutations: MutationTree<RootStateType> & Mutations = {
  SET_NAME(state, payload) {
    state.name = payload
  },
}

去vuex中的index.ts改造commit提示

import { CommitOptions, Store } from 'vuex'
import { Getters, getters } from './getters'
import { Mutations, mutations } from './mutations'
import { RouterGetters } from './router/getters'
import routerStore from './router/router'
import { RootStateType } from './type'

const store = new Store<RootStateType>({
  modules: {
    router: routerStore,
  },
  state: {
    name: 'test',
  },
  mutations: mutations,
  getters: getters,
})

export default store as Omit<Store<RootStateType>, 'getters' | 'commit'> & {
  getters: {
    [K in keyof Getters]: ReturnType<Getters[K]>
  } & {
    [K in keyof RouterGetters as `router/${K}`]: ReturnType<RouterGetters[K]>
  }
  commit: {
    <K extends keyof Mutations>(
      P: {
        type: K
        payload: Parameters<Mutations[K]>[1]
      },
      options?: CommitOptions
    ): void
  }
}

store调用


image.png
store.commit({
  type: 'SET_NAME',
  payload: 'name',
})

想要无脑输入type名
1、改造mutations

import { MutationTree } from 'vuex'
import { RootStateType } from './type'

export enum MutationNames {
  SET_NAME = 'SET_NAME',
}

export interface Mutations {
  [MutationNames.SET_NAME](state: RootStateType, payload: string): void
}

export const mutations: MutationTree<RootStateType> & Mutations = {
  [MutationNames.SET_NAME](state, payload) {
    state.name = payload
  },
}

2、改变调用


image.png
store.commit({
  type: MutationNames.SET_NAME,
  payload: 'name',
})

实现router模块下面的mutations,在vuex/router创建mutations.ts

import { MutationTree } from 'vuex'
import { RouterStateType } from './type'

export enum RouterMutationNames {
  SET_PATH = 'SET_PATH',
}

export interface RouterMutations {
  [RouterMutationNames.SET_PATH](state: RouterStateType, payload: string): void
}

export const routerMutations: MutationTree<RouterStateType> & RouterMutations =
  {
    [RouterMutationNames.SET_PATH](state, payload) {
      state.path = payload
    },
  }

改变vuex/index.ts的store类型

import { CommitOptions, Store } from 'vuex'
import { Getters, getters } from './getters'
import { Mutations, mutations } from './mutations'
import { RouterGetters } from './router/getters'
import { RouterMutations } from './router/mutations'
import routerStore from './router/router'
import { RootStateType } from './type'

const store = new Store<RootStateType>({
  modules: {
    router: routerStore,
  },
  state: {
    name: 'test',
  },
  mutations: mutations,
  getters: getters,
})

export default store as Omit<Store<RootStateType>, 'getters' | 'commit'> & {
  getters: {
    [K in keyof Getters]: ReturnType<Getters[K]>
  } & {
    [K in keyof RouterGetters as `router/${K}`]: ReturnType<RouterGetters[K]>
  }
  commit: {
    <K extends keyof Mutations>(
      P: {
        type: K
        payload: Parameters<Mutations[K]>[1]
      },
      options?: CommitOptions
    ): void
    <K extends keyof RouterMutations>(
      P: {
        type: `router/${K}`
        payload: Parameters<RouterMutations[K]>[1]
      },
      options?: CommitOptions
    ): void
  }
}

store调用:


image.png
store.commit({
  type: `router/${RouterMutationNames.SET_PATH}`,
  payload: 'path',
})

vuex/router/index.ts接入routerMutation

import { Module } from 'vuex'
import { RootStateType } from '../type'
import { routerMutations } from './mutations'
import { RouterStateType } from './type'

const routerStore: Module<RouterStateType, RootStateType> = {
  namespaced: true,
  state: {
    path: 'path',
  },
  mutations: routerMutations,
}

export default routerStore

store注入vue有点尴尬,只能这么写:

new Vue({
  router,
  store: store as Store<any>,
  render: (h) => h(App),
}).$mount('#app')

检验结果

console.log(store.state.name)
console.log(store.state.router?.path)
store.commit({
  type: `router/${RouterMutationNames.SET_PATH}`,
  payload: 'path',
})
console.log(store.state.router?.path)
store.commit({ type: MutationNames.SET_NAME, payload: 'new name' })
console.log(store.state.name)

好像不对


image.png

申明稍作改动

import { CommitOptions, Store } from 'vuex'
import { Getters, getters } from './getters'
import { Mutations, mutations } from './mutations'
import { RouterGetters } from './router/getters'
import { RouterMutations } from './router/mutations'
import routerStore from './router/router'
import { RootStateType } from './type'

const store = new Store<RootStateType>({
  modules: {
    router: routerStore,
  },
  state: {
    name: 'test',
  },
  mutations: mutations,
  getters: getters,
})

export default store as Omit<Store<RootStateType>, 'getters' | 'commit'> & {
  getters: {
    [K in keyof Getters]: ReturnType<Getters[K]>
  } & {
    [K in keyof RouterGetters as `router/${K}`]: ReturnType<RouterGetters[K]>
  }
  commit: {
    <K extends keyof Mutations>(
      type: K,
      payload: Parameters<Mutations[K]>[1],
      options?: CommitOptions
    ): void
    <K extends keyof RouterMutations>(
      type: `router/${K}`,
      payload: Parameters<RouterMutations[K]>[1],
      options?: CommitOptions
    ): void
  }
}

再次检验

console.log(store.state.name)
console.log(store.state.router?.path)
store.commit(`router/${RouterMutationNames.SET_PATH}`, 'new path')
console.log(store.state.router?.path)
store.commit(MutationNames.SET_NAME, 'new name')
console.log(store.state.name)
image.png

截止到这里mutation的变种也书写完成了

四、接下来,就是你们自己自由发挥的时间了
我们公司真正使用的语法如下:

store.commit(MutationTypes.UpdateLanguageVersion, 'en');
    store.moduleCommit(ModuleNames.Router, RouterMutationTypes.UpdateName, 'ease');
    store.dispatch(ActionTypes.UpdateUserInfo, {
      userType: true,
      isAdmin: 'admin',
      sysUserName: 'name',
      mabangExternalId: '001',
      companyId: 'c01',
      language: 'en',
    });
    store.moduleDispatch(ModuleNames.Router, RouterActionTypes.UpdateName, 'en');

@State(RootStateNames.Router) router!: RootStateTypes[RootStateNames.Router];
  @routerModule.State(RouterStateNames.Name) name!: RouterStateTypes[RouterStateNames.Name];
  @Getter(GetterNames.GetCount) getCount!: GetterTypes[GetterNames.GetCount];
  @routerModule.Getter(RouterGetterNames.GetName) getName!: RouterGetterTypes[RouterGetterNames.GetName];

最后表达:ts是利器,多写多看多练才能得心应手。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容