improve typing of actions
This commit is contained in:
parent
a272ee0329
commit
ce666b75bb
@ -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:
|
||||||
|
@ -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)));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,12 +7,16 @@ 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
|
||||||
|
? ReturnType<TPayloadGen> extends void
|
||||||
? (...args: Parameters<TPayloadGen>) => {
|
? (...args: Parameters<TPayloadGen>) => {
|
||||||
type: TType;
|
type: TType;
|
||||||
|
}
|
||||||
|
: (...args: Parameters<TPayloadGen>) => {
|
||||||
|
type: TType;
|
||||||
payload: ReturnType<TPayloadGen>;
|
payload: ReturnType<TPayloadGen>;
|
||||||
}
|
}
|
||||||
: (...args: any[]) => { type: TType; payload?: unknown });
|
: (...args: any[]) => { type: TType; payload?: unknown });
|
||||||
@ -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
42
src/types.test.ts
Normal 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'));
|
||||||
|
});
|
31
src/types.ts
31
src/types.ts
@ -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>
|
||||||
|
>;
|
||||||
|
Loading…
Reference in New Issue
Block a user