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
'check:prettier': prettier --check .
'prettier': prettier --check .
'check:prettier:fix': prettier --write .
'prettier:fix': prettier --write .
check:
deps: [tsc, test, 'check:prettier']
deps: [tsc, test, 'prettier']
'test:types': tsd
docs:

View File

@ -136,7 +136,10 @@ export class Updux<
if (typeof actionArg === 'function' && actionArg.type) {
this.#actions[type] = actionArg;
} 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];
}
setAction(type, payloadFunc?: Function) {
setAction(type, payloadFunc?: (...args: any) => any) {
const theAction = action(type, payloadFunc);
this.#actions = { ...this.#actions, [type]: theAction };
@ -404,7 +407,7 @@ export class Updux<
for (const action in this.actions) {
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<
TType extends string = string,
TPayloadGen = undefined
TPayloadGen = (...args: any[]) => any
> = {
type: TType;
} & (TPayloadGen extends (...args: any) => any
? (...args: Parameters<TPayloadGen>) => {
type: TType;
payload: ReturnType<TPayloadGen>;
}
? ReturnType<TPayloadGen> extends void
? (...args: Parameters<TPayloadGen>) => {
type: TType;
}
: (...args: Parameters<TPayloadGen>) => {
type: TType;
payload: ReturnType<TPayloadGen>;
}
: (...args: any[]) => { type: TType; payload?: unknown });
// interface Action {
@ -25,7 +29,14 @@ export type ActionGenerator<
// ): 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 result: Action = { type };
@ -40,5 +51,9 @@ export function action(type, payloadFunction = null) {
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 Upreducer<TState = any, TAction = Action> = (
action: Action
action: TAction
) => (state: TState) => TState;
export type Reducer<TState = any, TAction = Action> = (
state: TState | undefined,
action: Action
action: TAction
) => TState;
export type UnionToIntersection<U> = (
@ -22,11 +22,7 @@ type Subdux<TState = any> = {
};
type StateOf<D> = D extends { initial: infer I } ? I : unknown;
type ActionsOf<C> = C extends { actions: infer A }
? {
[K in keyof A]: Function;
}
: {};
type ActionsOf<C> = C extends { actions: infer A } ? A : {};
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 AggregateDuxActions<TActions, TSubduxes> = TActions &
UnionToIntersection<ActionsOf<ItemsOf<TSubduxes>>>;
type Actionize<TType extends string, TArgs> = TArgs extends ActionGenerator
? 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>
>;