feat!: use ts-action for action creation

typescript
Yanick Champoux 2020-02-04 12:02:28 -05:00
parent f6b7c2b15a
commit 6349d720b8
10 changed files with 88 additions and 99 deletions

View File

@ -1,4 +1,19 @@
# Updux concepts
# Updux concepts
## actions
Updux internally uses the package `ts-action` to create action creator
functions. Even if you don't use typescript, I recommend that you use it,
as it does what it does very well. But if you don't want to, no big deal.
Updux will recognize a function as an action creator if it has a `type`
property. So a homegrown creator could be as simple as:
```js
function action(type) {
return Object.assign( payload => ({type, payload}), { type } )
}
```
## effects

View File

@ -2,22 +2,23 @@
"dependencies": {
"lodash": "^4.17.15",
"mobx": "^5.14.2",
"redux": "^4.0.4"
"redux": "^4.0.4",
"ts-action": "^11.0.0",
"updeep": "^1.2.0"
},
"devDependencies": {
"docsify": "^4.10.2",
"docsify-cli": "^4.4.0",
"@babel/cli": "^7.6.4",
"@babel/core": "^7.6.4",
"@babel/preset-env": "^7.6.3",
"@types/jest": "^24.0.19",
"@types/lodash": "^4.14.144",
"babel-jest": "^24.9.0",
"docsify": "^4.10.2",
"docsify-cli": "^4.4.0",
"jest": "^24.9.0",
"ts-jest": "^24.1.0",
"tsd": "^0.10.0",
"typescript": "^3.6.4",
"updeep": "^1.2.0"
"typescript": "^3.6.4"
},
"license": "MIT",
"main": "dist/index.js",

View File

@ -1,26 +1,27 @@
import Updux, {actionCreator} from '.';
import { action, payload } from 'ts-action';
import u from 'updeep';
import Updux from '.';
const noopEffect = () => () => () => {};
test('actions defined in effects and mutations, multi-level', () => {
const bar = action('bar',(payload,meta) => ({payload,meta}) );
const foo = action('foo',(limit:number) => ({payload:{ limit} }) );
const {actions} = new Updux({
effects: {
foo: noopEffect,
},
mutations: {bar: () => () => null},
effects: [ [ foo, noopEffect ] ],
mutations: [ [ bar, () => () => null ] ],
subduxes: {
mysub: {
effects: {baz: noopEffect},
mutations: {quux: () => () => null},
actions: {
foo: (limit: number) => ({limit}),
foo
},
},
myothersub: {
effects: {
foo: noopEffect,
},
effects: [ [foo, noopEffect] ],
},
},
});
@ -32,7 +33,7 @@ test('actions defined in effects and mutations, multi-level', () => {
expect(actions.bar()).toEqual({type: 'bar'});
expect(actions.bar('xxx')).toEqual({type: 'bar', payload: 'xxx'});
expect(actions.bar(undefined, 'yyy')).toEqual({type: 'bar', meta: 'yyy'});
expect(actions.bar(undefined, 'yyy')).toEqual({type: 'bar', payload: undefined, meta: 'yyy'});
expect(actions.foo(12)).toEqual({type: 'foo', payload: {limit: 12}});
});
@ -41,23 +42,15 @@ describe('different calls to addAction', () => {
const updux = new Updux();
test('string', () => {
updux.addAction('foo');
updux.addAction( action('foo', payload() ));
expect(updux.actions.foo('yo')).toMatchObject({
type: 'foo',
payload: 'yo',
});
});
test('actionCreator', () => {
const bar = actionCreator('bar', null);
updux.addAction(bar);
expect(updux.actions.bar()).toMatchObject({
type: 'bar',
});
});
test('actionCreator inlined', () => {
updux.addAction('baz', (x) => ({x}));
updux.addAction( 'baz', (x) => ({payload: {x}}));
expect(updux.actions.baz(3)).toMatchObject({
type: 'baz', payload: { x: 3 }
});

View File

@ -1,4 +1,6 @@
import Updux, { actionCreator } from "./updux";
import { action } from 'ts-action';
import Updux from "./updux";
type MyState = {
sum: number;
@ -9,7 +11,7 @@ test("added mutation is present", () => {
initial: { sum: 0 }
});
const add = actionCreator("add", (n: number) => ({ n }));
const add = action("add", (n: number) => ({ payload: { n } }));
updux.addMutation(add, ({ n }, action) => ({ sum }) => ({ sum: sum + n }));

View File

@ -1,47 +1,9 @@
import fp from 'lodash/fp';
import {
Action,
ActionCreator,
ActionPayloadGenerator,
Dictionary,
} from '../types';
export function actionCreator<T extends string, P extends any>(
type: T,
transform: (...args: any[]) => P
): ActionCreator<T, P>;
export function actionCreator<T extends string>(
type: T,
transform: null
): ActionCreator<T, null>;
export function actionCreator<T extends string>(
type: T
): ActionCreator<T, undefined>;
export function actionCreator(type: any, transform?: any) {
if (transform) {
return Object.assign(
(...args: any[]) => ({ type, payload: transform(...args) }),
{ type }
);
}
if (transform === null) {
return Object.assign(() => ({ type }), { type });
}
return Object.assign((payload: unknown) => ({ type, payload }), { type });
}
export function actionFor(type: string): ActionCreator {
const f = (payload = undefined, meta = undefined) =>
fp.pickBy(v => v !== undefined)({ type, payload, meta }) as Action;
return Object.assign(f, {
_genericAction: true,
type,
});
}
type ActionPair = [string, ActionCreator];
function buildActions(actions: ActionPair[] = []): Dictionary<ActionCreator> {

View File

@ -3,6 +3,4 @@ import Updux from "./updux";
export { default as Updux } from "./updux";
export { UpduxConfig } from "./types";
export { actionCreator } from "./buildActions";
export default Updux;

View File

@ -1,5 +1,7 @@
import Updux, { actionCreator } from '.';
import u from 'updeep';
import { action, payload } from 'ts-action';
import Updux from '.';
import mwUpdux from './middleware_aux';
test('simple effect', () => {
@ -179,7 +181,7 @@ test('middleware as map', () => {
let rootState;
let rootFromChild;
const doIt = actionCreator('doIt');
const doIt = action('doIt', () => ({payload: ''}));
const child = new Updux({
initial: '',

View File

@ -1,7 +1,9 @@
import Updux, { actionCreator } from "./updux";
import { action } from 'ts-action';
import Updux from "./updux";
describe("as array of arrays", () => {
const doIt = actionCreator("doIt");
const doIt = action("doIt");
const updux = new Updux({
initial: "",

View File

@ -13,11 +13,6 @@ test('actions from mutations', () => {
expect(foo(true)).toEqual({type: 'foo', payload: true});
expect(foo({bar: 2}, {timestamp: 613})).toEqual({
type: 'foo',
payload: {bar: 2},
meta: {timestamp: 613},
});
});
test('reducer', () => {

View File

@ -1,7 +1,8 @@
import fp from "lodash/fp";
import u from "updeep";
import { action, payload } from 'ts-action';
import buildActions, { actionFor, actionCreator } from "./buildActions";
import buildActions from "./buildActions";
import buildInitial from "./buildInitial";
import buildMutations from "./buildMutations";
@ -24,7 +25,6 @@ import {
import { Middleware, Store, PreloadedState } from "redux";
import buildSelectors from "./buildSelectors";
export { actionCreator } from "./buildActions";
type StoreWithDispatchActions<
S = any,
@ -76,9 +76,9 @@ export class Updux<S = any> {
);
const actions = fp.getOr({}, "actions", config);
Object.entries(actions).forEach(([type, payload]: [string, any]): any =>
Object.entries(actions).forEach(([type, p]: [string, any]): any =>
this.addAction(
(payload as any).type ? payload : actionCreator(type, payload as any)
(p as any).type ? p : action(type, p)
)
);
@ -157,14 +157,23 @@ export class Updux<S = any> {
};
}
addMutation<A extends ActionCreator>(
addMutation<A extends ActionCreator=any>(
creator: A,
mutation: Mutation<S, A extends (...args: any[]) => infer R ? R : never>,
isSink?: boolean
) {
let c = fp.isFunction(creator) ? creator : actionFor(creator);
this.addAction(c);
)
addMutation<A extends ActionCreator=any>(
creator: string,
mutation: Mutation<S, any>,
isSink?: boolean
)
addMutation<A extends ActionCreator=any>(
creator,
mutation,
isSink
)
{
let c = this.addAction(creator);
this.localMutations[c.type] = [
this.groomMutations(mutation as any) as Mutation<S>,
@ -177,24 +186,34 @@ export class Updux<S = any> {
middleware: UpduxMiddleware<S>,
isGenerator: boolean = false
) {
let c = fp.isFunction(creator) ? creator : actionFor(creator);
this.addAction(c);
this.localActions[c.type] = c;
const c = this.addAction(creator);
this.localEffects.push([c.type, middleware, isGenerator]);
}
addAction(action: string, transform?: any): ActionCreator<string,any>
addAction(action: ActionCreator<any>, transform?: never): ActionCreator<string,any>
addAction(action: any,transform:any) {
if (typeof action === "string") {
if (!this.localActions[action]) {
this.localActions[action] = actionCreator(action,transform);
}
return this.localActions[action];
// can be
//addAction( actionCreator )
// addAction( 'foo', transform )
addAction(theaction: string, transform?: any): ActionCreator<string,any>
addAction(theaction: string|ActionCreator<any>, transform?: never): ActionCreator<string,any>
addAction(theaction: any,transform:any) {
if (typeof theaction === "string") {
if(transform !== undefined ) {
theaction = action(theaction,transform);
}
else {
theaction = this.actions[theaction] || action(theaction,payload())
}
}
return this.localActions[action.type] = action;
const already = this.actions[theaction.type];
if( already ) {
if ( already !== theaction ) {
throw new Error(`action ${theaction.type} already exists`)
}
return already;
}
return this.localActions[theaction.type] = theaction;
}
get _middlewareEntries() {