在提交或分派时有 IDE 建议突变和操作

Have IDE suggest mutations and actions when commiting or dispatching

目前我在 Vue 3 Typescript 项目中使用 Vuex。我有这样的东西:

import { createStore, useStore as baseUseStore, Store } from 'vuex';
import { InjectionKey } from 'vue';


export interface State {
    foo: string;
  }
  
  export const key: InjectionKey<Store<State>> = Symbol();
  
  export const store = createStore<State>({
      state: {foo: 'foo'},
      mutations: { 
        changeFoo(state: State, payload: string){
            state.foo = payload
        }
      },
      actions: { 
        setFooToBar({commit}){
         commit('changeFoo', 'bar')
      }}
  })

  export function useStoreTyped() {
    return baseUseStore(key);
  }
  
  

然后在组件中输入:

import { useStoreTyped } from "../store";

const store = useStoreTyped();

function() {
   store.distpatch('... // at this point I would like to see a list of my actions
}

此设置很好,因为在我的 IDE 中,如果我开始键入 store.state.,VS Code 将弹出一个小框,提示我的状态对象(在本例中为 .foo)上的道具。但是,当我尝试提交突变或调度操作时,我没有得到相同的行为。我怎样才能为 vuex 存储对象提供突变和动作名称,以便它可以为这些提供建议(智能感知)?

你能添加类型吗?是的你可以。您是否应该添加类型(而不是等待 vuex 添加更好的类型)是一个不同的问题。

基本解决方案

这是一种向 store 实例的 commitdispatch 方法添加类型提示的方法:

import { createStore, useStore as baseUseStore, Store, ActionHandler, DispatchOptions, CommitOptions } from 'vuex';
import { InjectionKey } from 'vue';


export interface State {
    foo: string;
}

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

const storeInitializer = {
    state: { foo: 'foo' } as State,
    mutations: {
        changeFoo(state: State, payload: string) {
            state.foo = payload
        },
        rearrangeFoo(state: State) {
            state.foo = state.foo.split('').sort().join()
        }
    },
    actions: {
        setFooToBar: (({ commit }) => {
            commit('changeFoo', 'bar')
        }) as ActionHandler<State, State>
    }
}

export type TypedDispatchAndAction<T extends { mutations: any, actions: any }> = {
  dispatch: (type: keyof T['actions'], payload?: any, options?: DispatchOptions) => Promise<any>
  commit: (type: keyof T['mutations'], payload?: any, options?:  CommitOptions) => void
}

export const store = createStore<State>(storeInitializer)

export function useStoreTyped() {
    const keyedStore = baseUseStore(key);

    return keyedStore as typeof keyedStore & TypedDispatchAndAction<typeof storeInitializer>
}

此方法有一些缺点:它不是很优雅,如果 mutationaction 需要 payload

,它不会进行完整类型检查

复杂的解决方案

此解决方案为 commitdispatchkey 参数添加了类型提示,并为 payload 参数

添加了类型检查
import { createStore, useStore as baseUseStore, Store, DispatchOptions, CommitOptions, ActionContext } from 'vuex';
import { InjectionKey } from 'vue';

type Length<L extends any[]> = L['length']

type Function<P extends any[] = any, R extends any = any> = (...args: P) => R

type ParamLength<Fn extends Function> = Length<Parameters<Fn>>

type P1<Fn extends (...args: any) => any> = Parameters<Fn>[1]

type Payload<H extends Function> = ParamLength<H> extends 2
    ? P1<H> extends infer X ? X : never
    : never

type TypedDispatchOrCommit<A extends { [key: string]: Function }, Options, Return, K extends keyof A = keyof A> = {
    <I extends K>(key: I, ...args: {
        0: [],
        1: [payload: Payload<A[I]>]
    }[Payload<A[I]> extends never ? 0 : 1]): Return
    <I extends K>(key: I, ...args: {
        0: [payload: undefined, options: Options],
        1: [payload: Payload<A[I]>, options: Options]
    }[Payload<A[I]> extends never ? 0 : 1]): Return
}

export type TypedDispatchAndAction<T extends { mutations: any, actions: any }> = {
  dispatch: TypedDispatchOrCommit<T['actions'], DispatchOptions, Promise<any>>
  commit: TypedDispatchOrCommit<T['mutations'], CommitOptions, void>
}



export interface State {
    foo: string;
}

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

const storeInitializer = {
    state: { foo: 'foo' } as State,
    mutations: {
        changeFoo(state: State, payload: string) {
            state.foo = payload
        },
        rearrangeFoo(state: State) {
            state.foo = state.foo.split('').sort().join()
        }
    },
    actions: {
        setFooToBar({ commit }: ActionContext<State, State>) {
            commit('changeFoo', 'bar')
        },
        setFoo({ commit }: ActionContext<State, State>, payload: string) {
            commit('changeFoo', payload)
        }
    }
}

export const store = createStore<State>(storeInitializer)

export function useStoreTyped() {
    const keyedStore = baseUseStore(key);

    return keyedStore as Omit<typeof keyedStore, 'dispatch' | 'commit'> & TypedDispatchAndAction<typeof storeInitializer>
}

尽管上面的代码不太好理解,但它确实通过了以下测试:

import { useTypedStore } from '../store'

const store = useTypedStore()

// expected: Pass, actual: Pass, returns void
store.commit('rearrangeFoo')
// expected: Fail, actual: Fail, returns void
store.commit('changeFoo')
// expected: Pass, actual: Pass, returns void
//    also gives typehint of string for payload
store.commit('changeFoo', 'bar')

dispatch也是一样,只是returns一个Promise<any>

结论

您可以获得类型,但最好的解决方案是 vuex 彻底检查其类型以获得更好的类型提示(假设符合他们的项目愿景)。

备注

这一切都是在 vs-code 上使用以下软件包完成的:

typescript@^4.5.5
vuex@^4.0.2
vue@^3.2.31