actions
This commit is contained in:
parent
003a1309bd
commit
fd064f5996
@ -4,6 +4,7 @@
|
|||||||
"@yanick/updeep": "../updeep",
|
"@yanick/updeep": "../updeep",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
"moize": "^6.1.0",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"ts-action": "^11.0.0",
|
"ts-action": "^11.0.0",
|
||||||
"ts-node": "^8.6.2",
|
"ts-node": "^8.6.2",
|
||||||
|
47
src/Updux.js
Normal file
47
src/Updux.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import moize from 'moize';
|
||||||
|
import u from '@yanick/updeep';
|
||||||
|
|
||||||
|
import { buildInitial } from './buildInitial/index.js';
|
||||||
|
import { buildActions } from './buildActions/index.js';
|
||||||
|
import { action } from './actions.js';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
* `Updux` is a way to minimize and simplify the boilerplate associated with the
|
||||||
|
* creation of a `Redux` store. It takes a shorthand configuration
|
||||||
|
* object, and generates the appropriate reducer, actions, middleware, etc.
|
||||||
|
* In true `Redux`-like fashion, upduxes can be made of sub-upduxes (`subduxes` for short) for different slices of the root state.
|
||||||
|
*/
|
||||||
|
export class Updux {
|
||||||
|
#initial = {};
|
||||||
|
#subduxes = {};
|
||||||
|
#actions = {};
|
||||||
|
|
||||||
|
constructor(config) {
|
||||||
|
this.#initial = config.initial ?? {};
|
||||||
|
|
||||||
|
this.#subduxes = config.subduxes ?? {};
|
||||||
|
|
||||||
|
this.#actions = config.actions ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
|
#memoInitial = moize( buildInitial );
|
||||||
|
#memoActions = moize(buildActions);
|
||||||
|
|
||||||
|
get initial() {
|
||||||
|
return this.#memoInitial(this.#initial,this.#subduxes);
|
||||||
|
}
|
||||||
|
|
||||||
|
get actions() {
|
||||||
|
return this.#memoActions(this.#actions, this.#subduxes);
|
||||||
|
}
|
||||||
|
|
||||||
|
addAction(type, payloadFunc) {
|
||||||
|
const theAction = action(type,payloadFunc);
|
||||||
|
|
||||||
|
this.#actions = u( { [type]: theAction }, this.#actions );
|
||||||
|
|
||||||
|
return theAction;
|
||||||
|
}
|
||||||
|
}
|
132
src/Updux.test.js
Normal file
132
src/Updux.test.js
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import { test } from 'tap';
|
||||||
|
|
||||||
|
import { Updux } from './Updux.js';
|
||||||
|
import { action } from './actions.js';
|
||||||
|
|
||||||
|
test('basic state', async (t) => {
|
||||||
|
const alpha = new Updux({
|
||||||
|
initial: { sub: 1 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const beta = new Updux({
|
||||||
|
initial: { foo: 1 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const dux = new Updux({
|
||||||
|
initial: { a: 1, b: 'two' },
|
||||||
|
subduxes: { alpha, beta },
|
||||||
|
});
|
||||||
|
|
||||||
|
t.same(dux.initial,{
|
||||||
|
a: 1,
|
||||||
|
b: 'two',
|
||||||
|
alpha: { sub: 1 },
|
||||||
|
beta: { foo: 1 },
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic actions', async (t) => {
|
||||||
|
const alpha = new Updux({
|
||||||
|
actions: {
|
||||||
|
foo: action('foo'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const beta = new Updux({});
|
||||||
|
|
||||||
|
const dux = new Updux({
|
||||||
|
subduxes: { alpha, beta },
|
||||||
|
actions: {
|
||||||
|
bar: action('bar' ),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
t.same(Object.keys(dux.actions).sort(),['bar', 'foo']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('addAction', async(t) => {
|
||||||
|
const dux = new Updux({
|
||||||
|
actions: {
|
||||||
|
bar: action('bar'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dux.addAction('foo');
|
||||||
|
|
||||||
|
t.same(Object.keys(dux.actions).sort(),['bar', 'foo']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic selectors', async(t) => {
|
||||||
|
const alpha = new Updux({
|
||||||
|
initial: { quux: 3 },
|
||||||
|
}).addSelector('getQuux', ({ quux }) => quux);
|
||||||
|
|
||||||
|
const dux = new Updux({
|
||||||
|
initial: {
|
||||||
|
foo: 1,
|
||||||
|
bar: 4,
|
||||||
|
},
|
||||||
|
subduxes: { alpha },
|
||||||
|
selectors: {
|
||||||
|
getBar: ({ bar }) => bar,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addSelector('getFoo', (state) => state.foo)
|
||||||
|
.addSelector('getAdd', ({ foo }) => (add) => add + foo)
|
||||||
|
.addAction('stuff');
|
||||||
|
|
||||||
|
t.equal(dux.selectors.getBar({ bar: 3 }), 3);
|
||||||
|
t.equal(dux.selectors.getFoo({ foo: 3 }) , 3);
|
||||||
|
|
||||||
|
t.equal(alpha.selectors.getQuux({ quux: 1 }),1);
|
||||||
|
t.equal(dux.selectors.getQuux({ alpha: { quux: 1 } }) ,1);
|
||||||
|
|
||||||
|
const store = dux.createStore();
|
||||||
|
|
||||||
|
t.equal(store.selectors.getFoo(),1);
|
||||||
|
t.equal(store.selectors.getQuux(),3);
|
||||||
|
t.equal(store.selectors.getAdd(7),8);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mutations', async () => {
|
||||||
|
|
||||||
|
const alpha = new Updux({
|
||||||
|
initial: { quux: 3 },
|
||||||
|
})
|
||||||
|
.addAction( 'add' )
|
||||||
|
.addMutation( 'add', ( toAdd ) => (state) => ({ quux: state.quux + toAdd }) );
|
||||||
|
|
||||||
|
const dux = new Updux({
|
||||||
|
initial: {
|
||||||
|
foo: 1,
|
||||||
|
bar: 4,
|
||||||
|
},
|
||||||
|
subduxes: { alpha },
|
||||||
|
})
|
||||||
|
.addAction( 'subtract' )
|
||||||
|
.addMutation( 'add', toAdd => state => ({ ...state, foo: state.foo + toAdd }) )
|
||||||
|
.addMutation( 'subtract', toSubtract => state => ({ ...state, bar: state.bar - toSubtract }) );
|
||||||
|
|
||||||
|
|
||||||
|
const store = dux.createStore();
|
||||||
|
|
||||||
|
t.same(store.getState(),{
|
||||||
|
foo: 1, bar: 4, alpha: { quux: 3 },
|
||||||
|
})
|
||||||
|
|
||||||
|
store.dispatch.add(10);
|
||||||
|
|
||||||
|
t.same(store.getState(), {
|
||||||
|
foo: 11, bar: 4, alpha: { quux: 13 },
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
store.dispatch.subtract(20);
|
||||||
|
|
||||||
|
t.same(store.getState(), {
|
||||||
|
foo: 11, bar: -16, alpha: { quux: 13 },
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
19
src/actions.js
Normal file
19
src/actions.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export function action( type, payloadFunction = null ) {
|
||||||
|
|
||||||
|
const generator = function(payloadArg) {
|
||||||
|
|
||||||
|
const result = { type };
|
||||||
|
|
||||||
|
if( payloadFunction )
|
||||||
|
result.payload = payloadFunction(payloadArg);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
generator.type = type;
|
||||||
|
|
||||||
|
return generator;
|
||||||
|
}
|
17
src/actions.test.js
Normal file
17
src/actions.test.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { test } from 'tap';
|
||||||
|
|
||||||
|
import { action } from './actions.js';
|
||||||
|
|
||||||
|
test( 'action generators', async (t) => {
|
||||||
|
|
||||||
|
const foo = action('foo');
|
||||||
|
|
||||||
|
t.equal( foo.type, 'foo' );
|
||||||
|
t.same( foo(), { type: 'foo' } );
|
||||||
|
|
||||||
|
const bar = action('bar');
|
||||||
|
|
||||||
|
t.equal( bar.type, 'bar' );
|
||||||
|
t.same( bar(), { type: 'bar' } );
|
||||||
|
|
||||||
|
} )
|
21
src/buildActions/index.js
Normal file
21
src/buildActions/index.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export function buildActions(actions={}, subduxes={}) {
|
||||||
|
// priority => generics => generic subs => craft subs => creators
|
||||||
|
|
||||||
|
const merged = { ...actions };
|
||||||
|
|
||||||
|
Object.values(subduxes).forEach(({ actions }) => {
|
||||||
|
if(!actions) return;
|
||||||
|
Object.entries(actions).forEach(([type, func]) => {
|
||||||
|
if (merged[type]) {
|
||||||
|
if (merged[type] === func) return;
|
||||||
|
throw new Error(
|
||||||
|
`trying to merge two different actions ${type}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
merged[type] = func;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
return merged;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user