feat!: use ts-action for action creation
This commit is contained in:
parent
f6b7c2b15a
commit
6349d720b8
@ -1,5 +1,20 @@
|
||||
# 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
|
||||
|
||||
Updux effects are a superset of redux middleware. I kept that format, and the
|
||||
|
11
package.json
11
package.json
@ -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",
|
||||
|
@ -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 }
|
||||
});
|
||||
|
@ -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 }));
|
||||
|
||||
|
@ -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> {
|
||||
|
@ -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;
|
||||
|
@ -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: '',
|
||||
|
@ -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: "",
|
||||
|
@ -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', () => {
|
||||
|
61
src/updux.ts
61
src/updux.ts
@ -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);
|
||||
// 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];
|
||||
}
|
||||
|
||||
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() {
|
||||
|
Loading…
Reference in New Issue
Block a user