feat!: use ts-action for action creation
This commit is contained in:
parent
f6b7c2b15a
commit
6349d720b8
@ -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
|
## effects
|
||||||
|
|
||||||
|
11
package.json
11
package.json
@ -2,22 +2,23 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"mobx": "^5.14.2",
|
"mobx": "^5.14.2",
|
||||||
"redux": "^4.0.4"
|
"redux": "^4.0.4",
|
||||||
|
"ts-action": "^11.0.0",
|
||||||
|
"updeep": "^1.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"docsify": "^4.10.2",
|
|
||||||
"docsify-cli": "^4.4.0",
|
|
||||||
"@babel/cli": "^7.6.4",
|
"@babel/cli": "^7.6.4",
|
||||||
"@babel/core": "^7.6.4",
|
"@babel/core": "^7.6.4",
|
||||||
"@babel/preset-env": "^7.6.3",
|
"@babel/preset-env": "^7.6.3",
|
||||||
"@types/jest": "^24.0.19",
|
"@types/jest": "^24.0.19",
|
||||||
"@types/lodash": "^4.14.144",
|
"@types/lodash": "^4.14.144",
|
||||||
"babel-jest": "^24.9.0",
|
"babel-jest": "^24.9.0",
|
||||||
|
"docsify": "^4.10.2",
|
||||||
|
"docsify-cli": "^4.4.0",
|
||||||
"jest": "^24.9.0",
|
"jest": "^24.9.0",
|
||||||
"ts-jest": "^24.1.0",
|
"ts-jest": "^24.1.0",
|
||||||
"tsd": "^0.10.0",
|
"tsd": "^0.10.0",
|
||||||
"typescript": "^3.6.4",
|
"typescript": "^3.6.4"
|
||||||
"updeep": "^1.2.0"
|
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
@ -1,26 +1,27 @@
|
|||||||
import Updux, {actionCreator} from '.';
|
import { action, payload } from 'ts-action';
|
||||||
import u from 'updeep';
|
import u from 'updeep';
|
||||||
|
|
||||||
|
import Updux from '.';
|
||||||
|
|
||||||
const noopEffect = () => () => () => {};
|
const noopEffect = () => () => () => {};
|
||||||
|
|
||||||
test('actions defined in effects and mutations, multi-level', () => {
|
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({
|
const {actions} = new Updux({
|
||||||
effects: {
|
effects: [ [ foo, noopEffect ] ],
|
||||||
foo: noopEffect,
|
mutations: [ [ bar, () => () => null ] ],
|
||||||
},
|
|
||||||
mutations: {bar: () => () => null},
|
|
||||||
subduxes: {
|
subduxes: {
|
||||||
mysub: {
|
mysub: {
|
||||||
effects: {baz: noopEffect},
|
effects: {baz: noopEffect},
|
||||||
mutations: {quux: () => () => null},
|
mutations: {quux: () => () => null},
|
||||||
actions: {
|
actions: {
|
||||||
foo: (limit: number) => ({limit}),
|
foo
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
myothersub: {
|
myothersub: {
|
||||||
effects: {
|
effects: [ [foo, noopEffect] ],
|
||||||
foo: noopEffect,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -32,7 +33,7 @@ test('actions defined in effects and mutations, multi-level', () => {
|
|||||||
|
|
||||||
expect(actions.bar()).toEqual({type: 'bar'});
|
expect(actions.bar()).toEqual({type: 'bar'});
|
||||||
expect(actions.bar('xxx')).toEqual({type: 'bar', payload: 'xxx'});
|
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}});
|
expect(actions.foo(12)).toEqual({type: 'foo', payload: {limit: 12}});
|
||||||
});
|
});
|
||||||
@ -41,23 +42,15 @@ describe('different calls to addAction', () => {
|
|||||||
const updux = new Updux();
|
const updux = new Updux();
|
||||||
|
|
||||||
test('string', () => {
|
test('string', () => {
|
||||||
updux.addAction('foo');
|
updux.addAction( action('foo', payload() ));
|
||||||
expect(updux.actions.foo('yo')).toMatchObject({
|
expect(updux.actions.foo('yo')).toMatchObject({
|
||||||
type: 'foo',
|
type: 'foo',
|
||||||
payload: 'yo',
|
payload: 'yo',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('actionCreator', () => {
|
|
||||||
const bar = actionCreator('bar', null);
|
|
||||||
updux.addAction(bar);
|
|
||||||
expect(updux.actions.bar()).toMatchObject({
|
|
||||||
type: 'bar',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('actionCreator inlined', () => {
|
test('actionCreator inlined', () => {
|
||||||
updux.addAction('baz', (x) => ({x}));
|
updux.addAction( 'baz', (x) => ({payload: {x}}));
|
||||||
expect(updux.actions.baz(3)).toMatchObject({
|
expect(updux.actions.baz(3)).toMatchObject({
|
||||||
type: 'baz', payload: { x: 3 }
|
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 = {
|
type MyState = {
|
||||||
sum: number;
|
sum: number;
|
||||||
@ -9,7 +11,7 @@ test("added mutation is present", () => {
|
|||||||
initial: { sum: 0 }
|
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 }));
|
updux.addMutation(add, ({ n }, action) => ({ sum }) => ({ sum: sum + n }));
|
||||||
|
|
||||||
|
@ -1,47 +1,9 @@
|
|||||||
import fp from 'lodash/fp';
|
import fp from 'lodash/fp';
|
||||||
import {
|
import {
|
||||||
Action,
|
|
||||||
ActionCreator,
|
ActionCreator,
|
||||||
ActionPayloadGenerator,
|
|
||||||
Dictionary,
|
Dictionary,
|
||||||
} from '../types';
|
} 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];
|
type ActionPair = [string, ActionCreator];
|
||||||
|
|
||||||
function buildActions(actions: ActionPair[] = []): Dictionary<ActionCreator> {
|
function buildActions(actions: ActionPair[] = []): Dictionary<ActionCreator> {
|
||||||
|
@ -3,6 +3,4 @@ import Updux from "./updux";
|
|||||||
export { default as Updux } from "./updux";
|
export { default as Updux } from "./updux";
|
||||||
export { UpduxConfig } from "./types";
|
export { UpduxConfig } from "./types";
|
||||||
|
|
||||||
export { actionCreator } from "./buildActions";
|
|
||||||
|
|
||||||
export default Updux;
|
export default Updux;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import Updux, { actionCreator } from '.';
|
|
||||||
import u from 'updeep';
|
import u from 'updeep';
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
|
||||||
|
import Updux from '.';
|
||||||
import mwUpdux from './middleware_aux';
|
import mwUpdux from './middleware_aux';
|
||||||
|
|
||||||
test('simple effect', () => {
|
test('simple effect', () => {
|
||||||
@ -179,7 +181,7 @@ test('middleware as map', () => {
|
|||||||
let rootState;
|
let rootState;
|
||||||
let rootFromChild;
|
let rootFromChild;
|
||||||
|
|
||||||
const doIt = actionCreator('doIt');
|
const doIt = action('doIt', () => ({payload: ''}));
|
||||||
|
|
||||||
const child = new Updux({
|
const child = new Updux({
|
||||||
initial: '',
|
initial: '',
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import Updux, { actionCreator } from "./updux";
|
import { action } from 'ts-action';
|
||||||
|
|
||||||
|
import Updux from "./updux";
|
||||||
|
|
||||||
describe("as array of arrays", () => {
|
describe("as array of arrays", () => {
|
||||||
const doIt = actionCreator("doIt");
|
const doIt = action("doIt");
|
||||||
|
|
||||||
const updux = new Updux({
|
const updux = new Updux({
|
||||||
initial: "",
|
initial: "",
|
||||||
|
@ -13,11 +13,6 @@ test('actions from mutations', () => {
|
|||||||
|
|
||||||
expect(foo(true)).toEqual({type: 'foo', payload: true});
|
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', () => {
|
test('reducer', () => {
|
||||||
|
61
src/updux.ts
61
src/updux.ts
@ -1,7 +1,8 @@
|
|||||||
import fp from "lodash/fp";
|
import fp from "lodash/fp";
|
||||||
import u from "updeep";
|
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 buildInitial from "./buildInitial";
|
||||||
import buildMutations from "./buildMutations";
|
import buildMutations from "./buildMutations";
|
||||||
|
|
||||||
@ -24,7 +25,6 @@ import {
|
|||||||
|
|
||||||
import { Middleware, Store, PreloadedState } from "redux";
|
import { Middleware, Store, PreloadedState } from "redux";
|
||||||
import buildSelectors from "./buildSelectors";
|
import buildSelectors from "./buildSelectors";
|
||||||
export { actionCreator } from "./buildActions";
|
|
||||||
|
|
||||||
type StoreWithDispatchActions<
|
type StoreWithDispatchActions<
|
||||||
S = any,
|
S = any,
|
||||||
@ -76,9 +76,9 @@ export class Updux<S = any> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const actions = fp.getOr({}, "actions", config);
|
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(
|
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,
|
creator: A,
|
||||||
mutation: Mutation<S, A extends (...args: any[]) => infer R ? R : never>,
|
mutation: Mutation<S, A extends (...args: any[]) => infer R ? R : never>,
|
||||||
isSink?: boolean
|
isSink?: boolean
|
||||||
) {
|
)
|
||||||
let c = fp.isFunction(creator) ? creator : actionFor(creator);
|
addMutation<A extends ActionCreator=any>(
|
||||||
|
creator: string,
|
||||||
this.addAction(c);
|
mutation: Mutation<S, any>,
|
||||||
|
isSink?: boolean
|
||||||
|
)
|
||||||
|
addMutation<A extends ActionCreator=any>(
|
||||||
|
creator,
|
||||||
|
mutation,
|
||||||
|
isSink
|
||||||
|
)
|
||||||
|
{
|
||||||
|
let c = this.addAction(creator);
|
||||||
|
|
||||||
this.localMutations[c.type] = [
|
this.localMutations[c.type] = [
|
||||||
this.groomMutations(mutation as any) as Mutation<S>,
|
this.groomMutations(mutation as any) as Mutation<S>,
|
||||||
@ -177,24 +186,34 @@ export class Updux<S = any> {
|
|||||||
middleware: UpduxMiddleware<S>,
|
middleware: UpduxMiddleware<S>,
|
||||||
isGenerator: boolean = false
|
isGenerator: boolean = false
|
||||||
) {
|
) {
|
||||||
let c = fp.isFunction(creator) ? creator : actionFor(creator);
|
const c = this.addAction(creator);
|
||||||
|
|
||||||
this.addAction(c);
|
|
||||||
this.localActions[c.type] = c;
|
|
||||||
this.localEffects.push([c.type, middleware, isGenerator]);
|
this.localEffects.push([c.type, middleware, isGenerator]);
|
||||||
}
|
}
|
||||||
|
|
||||||
addAction(action: string, transform?: any): ActionCreator<string,any>
|
// can be
|
||||||
addAction(action: ActionCreator<any>, transform?: never): ActionCreator<string,any>
|
//addAction( actionCreator )
|
||||||
addAction(action: any,transform:any) {
|
// addAction( 'foo', transform )
|
||||||
if (typeof action === "string") {
|
addAction(theaction: string, transform?: any): ActionCreator<string,any>
|
||||||
if (!this.localActions[action]) {
|
addAction(theaction: string|ActionCreator<any>, transform?: never): ActionCreator<string,any>
|
||||||
this.localActions[action] = actionCreator(action,transform);
|
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() {
|
get _middlewareEntries() {
|
||||||
|
Loading…
Reference in New Issue
Block a user