I'm creating a wrapper for redux-starter-kit to make it feel more like Vuex. The Github repo is here I'm working on this branch. This project is an experiment. The issue is I can not get these reducers type-checked correctly.
If you have an object of functions like so.
const reducers = {
addCounter(state:IState, payload:IActionPayload<number>):void {
state.counter += payload.payload;
}
}
And it goes through another function and takes the first parameter away. How do you keep the payload Type-checked?
I can do this with generics on a single function and typecast the return
I'm not sure how to do this dynamically for every function in an object.
After hacking away all day. I learned a lot about TypeScript Types and wow is it powerful and ugly. I'm still new to this so if someone has a better option or if I'm misunderstanding something please correct me.
IActionPayload<number>
// Formats a Reducer Function to remove state and place the
// payload parameter as the first parameter
type ReducerToAction <R> = R extends (...args: infer A) => any ? (payload:A[1]) => any : never
It works but it sure is ugly.
It is all about conditionals. If it is a function capture the args as A with the type of unknown[]
but because it is an array we can say A[1]
and get our payload. So if it is a function return type (payload:A[1]) => any
else never
You can loop over objects with this.
type loop<obj> = { [key in keyof obj]: obj[key] }
Now we need to do Step1 to obj[key]
type loop<obj> = { [key in keyof obj]: ReducerToAction<obj[key]> }
So it looks something like this.
type ReducersToActions<R> = { [K in keyof R]: ReducerToAction<R[K]> }
IActionPayload<number>
typeIf you provide the type
of what you are handling like so.
type IActionPayload <T> = { type: string, payload: T }
It seems you can access the keys. So if we place this also into a conditional we can pull the single key from the object.
type PullPayloadType<P> = P extends IActionPayload<any> ? P['payload'] : never
In English is should say if it is type IActionPayload
we know it has the key payload
so pull it else return never
.
With this, we will need to update the other types to take this in which will give you the following.
// Action Structure
type IActionPayload <T> = { type: string, payload: T }
// Reducers object structure
type IReduces = { [key:string] : (state:any, payload:IActionPayload<any>) => any }
// Gets the Payload type from an object that is of type IActionPayload
type PullPayloadType<P> = P extends IActionPayload<any> ? P['payload'] : never
// Formats a Reducer Function to remove state and place the payload parameter as the first parameter
type ReducerToAction<R> = R extends (...args: infer A) => any ? (payload:PullPayloadType<A[1]>) => any : never
// Formats Reducer Functions in a object that matches the type IReduces
type ReducersToActions<R extends IReduces> = { [K in keyof R]: ReducerToAction<R[K]> }