typescript对vuex的全面支持

wynnyo 2021年01月25日 891次浏览

Thanks

npm&github

简介

vue2 + typescript4.1.0

@xingbofeng 已经实现了 commit 和 dispatch 的支持了, 我在已有的功能上实现了 state 和 getter 的支持, 后续考虑是否可以实现 mapState, mapGetters, mapMutations, mapActions

现有功能

  • state支持
  • getter支持
  • commit支持
  • dispatch支持

待实现的功能

  • mapState
  • mapGetters
  • mapMutations
  • mapActions

功能实现

  • 对type的提取, 具体原理可以查看上面2篇文章, 这里只是源码, 我这里已经发布了npm版本, vuex-ts-prompt - npm (npmjs.com)

    type GetRestFuncType<T> = T extends (context: any, ...params: infer P) => infer R ? (...args: P) => R : never
    
    type AddPrefix<Keys, Prefix = ''> = `${Prefix & string}${Prefix extends '' ? '' : '/'}${Keys & string}`
    
    type GetStateTypes<Module> = Module extends { state: infer M }
    	? {
    			[StsteKey in keyof M]: M[StsteKey]
    	  }
    	: never
    
    type GetGettersTypes<Module, ModuleName = ''> = Module extends { getters: infer M }
    	? {
    			[GetterKey in keyof M as AddPrefix<GetterKey, ModuleName>]: ReturnType<M[GetterKey]>
    	  }
    	: never
    
    type GetMutationsTypes<Module, ModuleName = ''> = Module extends { mutations: infer M }
    	? {
    			[MutationKey in keyof M as AddPrefix<MutationKey, ModuleName>]: GetRestFuncType<M[MutationKey]>
    	  }
    	: never
    
    type GetActionsTypes<Module, ModuleName = ''> = Module extends { actions: infer M }
    	? {
    			[ActionKey in keyof M as AddPrefix<ActionKey, ModuleName>]: GetRestFuncType<M[ActionKey]>
    	  }
    	: never
    
    type GetModulesGetterTypes<Modules> = {
    	[K in keyof Modules]: GetGettersTypes<Modules[K], K>
    }[keyof Modules]
    
    type GetModulesMutationTypes<Modules> = {
    	[K in keyof Modules]: GetMutationsTypes<Modules[K], K>
    }[keyof Modules]
    
    type GetModulesActionTypes<Modules> = {
    	[K in keyof Modules]: GetActionsTypes<Modules[K], K>
    }[keyof Modules]
    
    type GetSubModuleStateTypes<Module> = Module extends { modules: infer SubModules }
    	? {
    			[K in keyof SubModules]: GetStateTypes<SubModules[K]>
    	  }
    	: never
    
    type GetSubModuleGettersTypes<Module> = Module extends { modules: infer SubModules }
    	? {
    			[K in keyof SubModules]: GetGettersTypes<SubModules[K], K>
    	  }[keyof SubModules]
    	: never
    
    type GetSubModuleMutationsTypes<Module> = Module extends { modules: infer SubModules } ? GetModulesMutationTypes<SubModules> : never
    
    type GetSubModuleActionsTypes<Module> = Module extends { modules: infer SubModules } ? GetModulesActionTypes<SubModules> : never
    
    type UnionToIntersection<T> = (T extends any ? (k: T) => void : never) extends (k: infer I) => void ? I : never
    
    type GetTypeOfKey<T, K extends keyof T> = {
    	[Key in keyof T]: K extends keyof T ? T[K] : never
    }[keyof T]
    
    type GetParam<T> = T extends () => any ? undefined : T extends (arg: infer R) => any ? R : any
    
    type ReturnType<T> = T extends (...args: any) => infer R ? R : any
    
    export type GetStateType<R> = UnionToIntersection<GetSubModuleStateTypes<R> | GetStateTypes<R>>
    
    export type GetGettersType<R> = UnionToIntersection<GetSubModuleGettersTypes<R> | GetGettersTypes<R>>
    
    export type GetMutationsType<R> = UnionToIntersection<GetSubModuleMutationsTypes<R> | GetMutationsTypes<R>>
    
    export type GetActionsType<R> = UnionToIntersection<GetSubModuleActionsTypes<R> | GetActionsTypes<R>>
    
    export type GetPayLoad<T, K extends keyof T> = GetParam<GetTypeOfKey<T, K>>
    
    export type GetReturnType<T, K extends keyof T> = ReturnType<GetTypeOfKey<T, K>>
    
    
  • 新建 store/modules/app.ts

    import { ActionContext } from 'vuex'
    
    export interface State {
    	app: string
    }
    
    const state: State = {
    	app: 'myapp'
    }
    
    const getters = {
    	app: (state: State) => state.app,
    	getApp: (state: State) => (s: string) => state.app
    }
    
    const mutations = {
    	SET_APP: (state: State, payload: string) => {
    		state.app = payload
    	}
    }
    
    const actions = {
    	setApp({ commit }: ActionContext<State, any>, payload: string) {
    		commit('SET_APP', payload)
    	}
    }
    
    export default {
    	namespaced: true,
    	state,
    	getters,
    	mutations,
    	actions
    }
    
  • 新建 store/index.ts

    import Vue from 'vue'
    import Vuex, { ActionContext, CommitOptions, DispatchOptions } from 'vuex'
    import { GetActionsType, GetGettersType, GetMutationsType, GetPayLoad, GetReturnType, GetStateType } from 'vuex-ts-prompt'
    import createLogger from 'vuex/dist/logger'
    import app from './modules/app'
    
    Vue.use(Vuex)
    
    const state = {
    	name: 'wynnyo'
    }
    
    type RootState = typeof state
    
    const getters = {
    	name: (state: RootState) => state.name,
    	fullname: (state: RootState) => (surname: string) => surname + state.name 
    }
    
    const mutations = {
    	SET_NAME: (state: RootState, payload: string) => {
    		state.name = payload
    	}
    }
    
    const actions = {
    	setName({ commit }: ActionContext<RootState, any>, payload: string) {
    		commit('SET_NAME', payload)
    	}
    }
    
    const modules = {
    	app,
    }
    
    const vuexOptions = {
    	state,
    	getters,
    	mutations,
    	actions,
    	modules,
    	plugins: process.env.NODE_ENV === 'development' ? [createLogger()] : []
    }
    
    type Mutations = GetMutationsType<typeof vuexOptions>
    
    type Actions = GetActionsType<typeof vuexOptions>
    
    // 导出 state 类型
    export type State = GetStateType<typeof vuexOptions>
    // 导出 getters 类型
    export type Getters = GetGettersType<typeof vuexOptions>
    // 导出 commit 类型
    export interface Commit {
    	<T extends keyof Mutations>(type: T, payload?: GetPayLoad<Mutations, T>, options?: CommitOptions): GetReturnType<Mutations, T>
    }
    // 导出 dispatch 类型
    export interface Dispatch {
    	<T extends keyof Actions>(type: T, payload?: GetPayLoad<Actions, T>, options?: DispatchOptions): Promise<GetReturnType<Actions, T>>
    }
    
    // state?: S | (() => S);
    // getters?: GetterTree<S, S>;
    // 这里把 state 类型设置为 any 是为了让参数不受约束
    const store = new Vuex.Store<any>(vuexOptions)
    
    export default store
    
  • 新建 types/store.d.ts

    import { Commit, Dispatch, Getters, State } from '@/store'
    
    declare module 'vue/types/vue' {
    	export declare class Store<S> {
    		state: State
    		getters: Getters
    		dispatch: Dispatch
    		commit: Commit
    	}
    
    	interface Vue {
    		$store: Store<State>
    	}
    }
    
  • 修改 main.ts

    import store from './store'
    Vue.prototype.$store = store
    

使用截图

  • 全局变量 $store

    image-20210125121827015

  • state

    image-20210125121850658

    image-20210125122153256

  • getters

    image-20210125122249448

    image-20210125122330124

    image-20210125122422204

  • commit

    image-20210125122538834

    image-20210125122616232

    image-20210125122648042

  • dispatch

    image-20210125122737544

    image-20210125122803347

    image-20210125122834526