-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: improve helper types for more type safety #1121
Changes from 1 commit
0768c80
25775c5
21ee399
eee1f3c
f94cf70
a5c4e26
58d28a5
0858c6d
1e27c5e
c2068f3
7abf34f
9564b80
1aa407f
cfb6042
00360b5
9b89ae7
09475d5
8e0c60b
b14662c
b24d744
8b6a6f9
9b183f0
715eaad
fc1f29b
c3626f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,8 +6,6 @@ import { Dispatch, Commit } from './index'; | |
*/ | ||
type Computed<R> = () => R; | ||
type Method<R> = (...args: any[]) => R; | ||
type MutationMethod<P> = (payload: P) => void; | ||
type ActionMethod<P> = (payload: P) => Promise<any>; | ||
type CustomVue = Vue & Record<string, any>; | ||
|
||
interface BaseType { [key: string]: any } | ||
|
@@ -20,6 +18,26 @@ interface BaseMethodMap<F> { | |
[key: string]: (this: CustomVue, fn: F, ...args: any[]) => any; | ||
} | ||
|
||
type MethodType = 'optional' | 'normal' | ||
|
||
/** | ||
* Return component method type for a mutation. | ||
* You can specify `Type` to choose whether the argument is optional or not. | ||
*/ | ||
type MutationMethod<P, Type extends MethodType> = { | ||
optional: (payload?: P) => void; | ||
normal: (payload: P) => void; | ||
}[Type]; | ||
|
||
/** | ||
* Return component method type for an action. | ||
* You can specify `Type` to choose whether the argument is optional or not. | ||
*/ | ||
type ActionMethod<P, Type extends MethodType> = { | ||
optional: (payload?: P) => Promise<any>; | ||
normal: (payload: P) => Promise<any>; | ||
}[Type]; | ||
|
||
/** | ||
* mapGetters | ||
*/ | ||
|
@@ -51,50 +69,50 @@ interface RootMapState<State, Getters> extends MapState<State, Getters> { | |
/** | ||
* mapMutations | ||
*/ | ||
interface MapMutations<Mutations> { | ||
<Key extends keyof Mutations>(map: Key[]): { [K in Key]: MutationMethod<Mutations[K]> }; | ||
<Map extends Record<string, keyof Mutations>>(map: Map): { [K in keyof Map]: MutationMethod<Mutations[Map[K]]> }; | ||
interface MapMutations<Mutations, Type extends MethodType> { | ||
<Key extends keyof Mutations>(map: Key[]): { [K in Key]: MutationMethod<Mutations[K], Type> }; | ||
<Map extends Record<string, keyof Mutations>>(map: Map): { [K in keyof Map]: MutationMethod<Mutations[Map[K]], Type> }; | ||
<Map extends BaseMethodMap<Commit<Mutations>>>(map: Map): { [K in keyof Map]: Method<any> }; | ||
} | ||
|
||
interface RootMapMutations<Mutations> extends MapMutations<Mutations> { | ||
<Key extends keyof Mutations>(namespace: string, map: Key[]): { [K in Key]: MutationMethod<Mutations[K]> }; | ||
<Map extends Record<string, keyof Mutations>>(namespace: string, map: Map): { [K in keyof Map]: MutationMethod<Mutations[Map[K]]> }; | ||
interface RootMapMutations<Mutations, Type extends MethodType> extends MapMutations<Mutations, Type> { | ||
<Key extends keyof Mutations>(namespace: string, map: Key[]): { [K in Key]: MutationMethod<Mutations[K], Type> }; | ||
<Map extends Record<string, keyof Mutations>>(namespace: string, map: Map): { [K in keyof Map]: MutationMethod<Mutations[Map[K]], Type> }; | ||
<Map extends BaseMethodMap<Commit<Mutations>>>(namespace: string, map: Map): { [K in keyof Map]: Method<any> }; | ||
} | ||
|
||
/** | ||
* mapActions | ||
*/ | ||
interface MapActions<Actions> { | ||
<Key extends keyof Actions>(map: Key[]): { [K in Key]: ActionMethod<Actions[K]> }; | ||
<Map extends Record<string, keyof Actions>>(map: Map): { [K in keyof Map]: ActionMethod<Actions[Map[K]]> }; | ||
interface MapActions<Actions, Type extends MethodType> { | ||
<Key extends keyof Actions>(map: Key[]): { [K in Key]: ActionMethod<Actions[K], Type> }; | ||
<Map extends Record<string, keyof Actions>>(map: Map): { [K in keyof Map]: ActionMethod<Actions[Map[K]], Type> }; | ||
<Map extends BaseMethodMap<Dispatch<Actions>>>(map: Map): { [K in keyof Map]: Method<any> }; | ||
} | ||
|
||
interface RootMapActions<Actions> extends MapActions<Actions> { | ||
<Key extends keyof Actions>(namespace: string, map: Key[]): { [K in Key]: ActionMethod<Actions[K]> }; | ||
<Map extends Record<string, keyof Actions>>(namespace: string, map: Map): { [K in keyof Map]: ActionMethod<Actions[Map[K]]> }; | ||
interface RootMapActions<Actions, Type extends MethodType> extends MapActions<Actions, Type> { | ||
<Key extends keyof Actions>(namespace: string, map: Key[]): { [K in Key]: ActionMethod<Actions[K], Type> }; | ||
<Map extends Record<string, keyof Actions>>(namespace: string, map: Map): { [K in keyof Map]: ActionMethod<Actions[Map[K]], Type> }; | ||
<Map extends BaseMethodMap<Dispatch<Actions>>>(namespace: string, map: Map): { [K in keyof Map]: Method<any> }; | ||
} | ||
|
||
/** | ||
* namespaced helpers | ||
*/ | ||
interface NamespacedMappers<State, Getters, Mutations, Actions> { | ||
interface NamespacedMappers<State, Getters, Mutations, Actions, Type extends MethodType> { | ||
mapState: MapState<State, Getters>; | ||
mapGetters: MapGetters<Getters>; | ||
mapMutations: MapMutations<Mutations>; | ||
mapActions: MapActions<Actions>; | ||
mapMutations: MapMutations<Mutations, Type>; | ||
mapActions: MapActions<Actions, Type>; | ||
} | ||
|
||
export declare const mapState: RootMapState<BaseType, BaseType>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The proposed usage is Another usage is that we export export interface RootMapState<State> {
<Keys extends keyof State>(keys: Keys[]): {[K in Keys]: State[K]}
} then users can write something like But it is more versatile in usage -- I think object style can be supported, and more precise in returning type. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about reusing the pattern of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That sounds good idea. I think returning root helpers from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Awesome! Great balance between types and runtime behavior. |
||
|
||
export declare const mapMutations: RootMapMutations<BaseType>; | ||
export declare const mapMutations: RootMapMutations<BaseType, 'optional'>; | ||
|
||
export declare const mapGetters: RootMapGetters<BaseType>; | ||
|
||
export declare const mapActions: RootMapActions<BaseType>; | ||
export declare const mapActions: RootMapActions<BaseType, 'optional'>; | ||
|
||
export declare function createNamespacedHelpers(namespace?: string): NamespacedMappers<BaseType, BaseType, BaseType, BaseType>; | ||
export declare function createNamespacedHelpers<State, Getters, Mutations, Actions>(namespace?: string): NamespacedMappers<State, Getters, Mutations, Actions>; | ||
export declare function createNamespacedHelpers(namespace?: string): NamespacedMappers<BaseType, BaseType, BaseType, BaseType, 'optional'>; | ||
export declare function createNamespacedHelpers<State, Getters, Mutations, Actions>(namespace?: string): NamespacedMappers<State, Getters, Mutations, Actions, 'normal'>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This typing still requires all methods in mutation/action are homogeneous: all methods either require one parameter at the same time or don't accept parameter at all. We cannot declare such mutations that some methods require parameter while others don't at the same time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I intend that behavior. I leave them optional if the users do not annotate types because they probably want flexible syntax like in JS. On the other hand, the methods always require an argument if they annotate types because they probably want type safety in that case.