improve typing of actions

typescript
Yanick Champoux 2021-10-19 12:41:23 -04:00
parent a272ee0329
commit ce666b75bb
5 changed files with 94 additions and 23 deletions

View File

@ -13,12 +13,12 @@ tasks:
test: jest test: jest
'check:prettier': prettier --check . 'prettier': prettier --check .
'check:prettier:fix': prettier --write . 'prettier:fix': prettier --write .
check: check:
deps: [tsc, test, 'check:prettier'] deps: [tsc, test, 'prettier']
'test:types': tsd 'test:types': tsd
docs: docs:

View File

@ -136,7 +136,10 @@ export class Updux<
if (typeof actionArg === 'function' && actionArg.type) { if (typeof actionArg === 'function' && actionArg.type) {
this.#actions[type] = actionArg; this.#actions[type] = actionArg;
} else { } else {
this.#actions[type] = action(type, actionArg); const args = Array.isArray(actionArg)
? actionArg
: [actionArg];
this.#actions[type] = action(type, ...args);
} }
} }
} }
@ -226,7 +229,7 @@ export class Updux<
this.#reactions = [...this.#reactions, subscription]; this.#reactions = [...this.#reactions, subscription];
} }
setAction(type, payloadFunc?: Function) { setAction(type, payloadFunc?: (...args: any) => any) {
const theAction = action(type, payloadFunc); const theAction = action(type, payloadFunc);
this.#actions = { ...this.#actions, [type]: theAction }; this.#actions = { ...this.#actions, [type]: theAction };
@ -404,7 +407,7 @@ export class Updux<
for (const action in this.actions) { for (const action in this.actions) {
store.dispatch[action] = (...args) => { store.dispatch[action] = (...args) => {
return store.dispatch(this.actions[action](...args)); return store.dispatch(this.actions[action](...(args as any)));
}; };
} }

View File

@ -7,14 +7,18 @@ export type Action<T extends string = string, TPayload = any> = {
export type ActionGenerator< export type ActionGenerator<
TType extends string = string, TType extends string = string,
TPayloadGen = undefined TPayloadGen = (...args: any[]) => any
> = { > = {
type: TType; type: TType;
} & (TPayloadGen extends (...args: any) => any } & (TPayloadGen extends (...args: any) => any
? (...args: Parameters<TPayloadGen>) => { ? ReturnType<TPayloadGen> extends void
type: TType; ? (...args: Parameters<TPayloadGen>) => {
payload: ReturnType<TPayloadGen>; type: TType;
} }
: (...args: Parameters<TPayloadGen>) => {
type: TType;
payload: ReturnType<TPayloadGen>;
}
: (...args: any[]) => { type: TType; payload?: unknown }); : (...args: any[]) => { type: TType; payload?: unknown });
// interface Action { // interface Action {
@ -25,7 +29,14 @@ export type ActionGenerator<
// ): ActionGenerator<T, F>; // ): ActionGenerator<T, F>;
// } // }
export function action(type, payloadFunction = null) { export function action<
TType extends string,
TPayload extends (...args: any) => any
>(
type: TType,
payloadFunction?: TPayload,
transformer?: Function
): ActionGenerator<TType, TPayload> {
const generator = function (...payloadArg) { const generator = function (...payloadArg) {
const result: Action = { type }; const result: Action = { type };
@ -40,5 +51,9 @@ export function action(type, payloadFunction = null) {
generator.type = type; generator.type = type;
return generator; return (
transformer
? (...args: any) => transformer(generator(...args), args)
: generator
) as any;
} }

42
src/types.test.ts Normal file
View File

@ -0,0 +1,42 @@
import { Updux } from './Updux';
import { Action, action, ActionGenerator } from './actions';
import { expectAssignable, expectType, expectNotType } from 'tsd';
type SimplePayload = (x: number) => number;
test('dux actions', () => {
const a1 = action('a1', (x: number) => x);
const inner = new Updux({
actions: {
a7: (x: string) => x,
},
});
const dux = new Updux({
actions: {
a1,
a2: () => {},
a5: (x: number) => x,
a6: action('a6', undefined, (x) => ({
...x,
meta: 'bob',
})),
},
subduxes: {
inner,
},
});
expectAssignable<ActionGenerator<'a1', SimplePayload>>(dux.actions.a1);
expectAssignable<ActionGenerator<'a5', SimplePayload>>(dux.actions.a5);
expectAssignable<Action<'a2'>>(dux.actions.a2());
expectType<{ payload: number }>(dux.actions.a1(12));
expectType<{ type: 'a2' }>(dux.actions.a2());
expectType<{ type: 'a6' }>(dux.actions.a6());
expectType<{ type: 'a7'; payload: string }>(dux.actions.a7('potato'));
});

View File

@ -1,14 +1,14 @@
import { Action } from './actions'; import { Action, ActionGenerator } from './actions';
export type Dict<T> = Record<string, T>; export type Dict<T> = Record<string, T>;
export type Upreducer<TState = any, TAction = Action> = ( export type Upreducer<TState = any, TAction = Action> = (
action: Action action: TAction
) => (state: TState) => TState; ) => (state: TState) => TState;
export type Reducer<TState = any, TAction = Action> = ( export type Reducer<TState = any, TAction = Action> = (
state: TState | undefined, state: TState | undefined,
action: Action action: TAction
) => TState; ) => TState;
export type UnionToIntersection<U> = ( export type UnionToIntersection<U> = (
@ -22,11 +22,7 @@ type Subdux<TState = any> = {
}; };
type StateOf<D> = D extends { initial: infer I } ? I : unknown; type StateOf<D> = D extends { initial: infer I } ? I : unknown;
type ActionsOf<C> = C extends { actions: infer A } type ActionsOf<C> = C extends { actions: infer A } ? A : {};
? {
[K in keyof A]: Function;
}
: {};
type Subduxes = Record<string, Subdux>; type Subduxes = Record<string, Subdux>;
@ -44,5 +40,20 @@ type DuxActionsSubduxes<C> = C extends object ? ActionsOf<C[keyof C]> : unknown;
export type ItemsOf<C> = C extends object ? C[keyof C] : unknown; export type ItemsOf<C> = C extends object ? C[keyof C] : unknown;
export type AggregateDuxActions<TActions, TSubduxes> = TActions & type Actionize<TType extends string, TArgs> = TArgs extends ActionGenerator
UnionToIntersection<ActionsOf<ItemsOf<TSubduxes>>>; ? TArgs
: TArgs extends any[]
? ActionGenerator<TType, TArgs[0]>
: ActionGenerator<TType, TArgs>;
export type MergeDuxActions<TActions, TSubduxes> = UnionToIntersection<
ActionsOf<TSubduxes[keyof TSubduxes]> | TActions
>;
export type AggregateDuxActionsInner<TActions> = {
[K in keyof TActions]: K extends string ? Actionize<K, TActions[K]> : never;
};
export type AggregateDuxActions<TActions, TSubduxes> = AggregateDuxActionsInner<
MergeDuxActions<TActions, TSubduxes>
>;