actions
This commit is contained in:
parent
003a1309bd
commit
fd064f5996
@ -4,6 +4,7 @@
|
||||
"@yanick/updeep": "../updeep",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash-es": "^4.17.21",
|
||||
"moize": "^6.1.0",
|
||||
"redux": "^4.0.5",
|
||||
"ts-action": "^11.0.0",
|
||||
"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