在提交或分派时有 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
实例的 commit
和 dispatch
方法添加类型提示的方法:
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>
}
此方法有一些缺点:它不是很优雅,如果 mutation
或 action
需要 payload
,它不会进行完整类型检查
复杂的解决方案
此解决方案为 commit
和 dispatch
的 key
参数添加了类型提示,并为 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
目前我在 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
实例的 commit
和 dispatch
方法添加类型提示的方法:
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>
}
此方法有一些缺点:它不是很优雅,如果 mutation
或 action
需要 payload
复杂的解决方案
此解决方案为 commit
和 dispatch
的 key
参数添加了类型提示,并为 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