|
import { ReducersMapObject, Action, Reducer, AnyAction, DeepPartial } from "redux"; |
|
|
|
/** |
|
* A reducer that returns a partial state |
|
*/ |
|
export type PartialReducer<S, A extends Action<string>> = (store: S, action: A) => DeepPartial<S>; |
|
|
|
/** |
|
* Given an action creator collection, infers the types of all of the contained action creators |
|
*/ |
|
export type ActionsOf<T> = T[keyof T] extends (...args: any[]) => any |
|
? ReturnType<T[keyof T]> extends Action<string> ? ReturnType<T[keyof T]> : never |
|
: never; |
|
|
|
/** |
|
* Given an action creator collection and a field name in that collection, infers the type |
|
* of the action associated with that field name |
|
*/ |
|
export type Actionable<T, K extends keyof T = keyof T> = T[K] extends (...args: any[]) => any |
|
? ReturnType<T[K]> extends Action<string> ? ReturnType<T[K]> : never |
|
: never; |
|
|
|
/** |
|
* Extracts the action key type from an action |
|
*/ |
|
export type ActionKey<T> = T extends Action<infer Key> ? Key : never; |
|
|
|
/** |
|
* Picks an action out of an ActionsOf<T> based on the ActionKey |
|
*/ |
|
export type PickAction<T, Key extends ActionKey<T>> = T extends Action<Key> ? T : never; |
|
|
|
/** |
|
* Given a store and a set of actions, creates the type of all reducers |
|
* for those actions. This gets Partial'd later on, but is nicer to see |
|
* in its entirety here |
|
*/ |
|
export type ReducersForActions<S, Actions extends Action<string>> = { |
|
[K in ActionKey<Actions>]: PartialReducer<S, PickAction<Actions, K>> |
|
}; |
|
|
|
/** |
|
* The type of an action creator |
|
*/ |
|
export type Creator<Key extends string, T> = (payload: T) => Action<Key> & T; |
|
|
|
/** |
|
* Creates an action given the key and the payload, the trick |
|
* here is the Key parameter which gets inferred based on the |
|
* string passed in |
|
*/ |
|
export function creator<Key extends string, T extends object>( |
|
key: Key, |
|
payload: T |
|
): Action<Key> & T { |
|
return Object.assign({ type: key }, payload); |
|
} |
|
|
|
export class ReducerManager<Store> { |
|
/** |
|
* Must be parameterized based on a set of actions, and |
|
* given a name since I keep up with all of my reducers by |
|
* name. Don't register two reducers with the same name. |
|
*/ |
|
public makeReducer = <Actions extends Action<string>>( |
|
name: string, |
|
reducers: Partial<ReducersForAction<Store, Actions>> |
|
): Reducer<Store, Actions> => { |
|
// implements the logic to register the reducers, it's |
|
// not terribly complicated, but this is the point where |
|
// I'm not sure how much I can share. Gotta leave some |
|
// homework for the reader anyway =P |
|
} |
|
// This is called when setting up the Redux context |
|
public getGlobalReducer = () => {} |
|
public registerReducer = (name: string, reducer: Reducer<Store>) => {} |
|
} |
|
|
|
// I don't do it exactly like this, but the concept is pretty much the same. |
|
// There's a single global reducer manager that gets instantiated for my |
|
// store, then I export the makeReducer for that instance of the manager. |
|
export const REDUCER_MANAGER = ReducerManager<MyStore>(initialState); |
|
export const makeReducer = REDUCER_MANAGER.makeReducer; |