Simple flux implementation.
Alx provides only Dispatcher
and UseCase
module. Dispatcher
is like a EventEmitter
for Alx. UseCase
is a logic component included action
, reducer(s)
and next process named chain
.
Alx does not provide Store
. You can impletemt store what you want.
npm install --save alx
- context: Domain context of state.
- status: Set of state.
- state: State of context.
const status = {
context: { state }
}
- action: Function that return payload.
- payload: Something that returned from action.
function action(count = 1) {
return { count }
}
const payload = action();
- usecase: Component of action, reducer(s) and chain.
- reducer: Function that return next status.
- reducers: Function that return next state.
- chain: Function of next process depend on the usecase
import { UseCase } from "alx";
// helper functions of UseCase
const { compose, link, clone } = UseCase.initialize();
UseCase
is a component of action
, reducer(s)
and chain
. chain is that next process depend on the usecase.
import { UseCase } from "alx";
const { compose, link, clone } = UseCase.initialize();
/* usecase */
const increment = compose((u) => {
u.id = "INCREMENT";
u.action = (count = 1) => ({ count });
u.reducer = (status, payload) => ({
counter: { count: status.counter.count + payload.count }
});
})
compose
is a wrapper function of UseCaseClass.
import assert from 'assert';
import { UseCase } from 'alx';
assert(increment.usecase instanceof UseCase)
Dispatcher is like a EventEmitter for alx.
import { Disptcher } from 'alx';
const dispatcher = new Dispatcher();
const dispatch = dispatcher.dispatch.bind(dispatcher);
You can subsribe 'USECASE:ACTION'
or 'ERROR'
events from Dispatcher.
Dispatcher publishes usecase
instance and payload
on 'USECASE:ACTION'
event or error
on ERRPR
event.
dispatcher.subscribe('USECASE:ACTION', ({usecase, payload}) => {
...
});
dispatcher.subscribe('ERROR', (error) => {
...
});
For update status, You can use UseCase#reduce
in USECASE:ACTION
event handler of Dispatcher. UseCase#reduce
implements usecase.reducer(s)
and return nextStatus.
/* initial status */
let status = {
counter: { count: 0 }
};
dispatcher.subscribe('USECASE:ACTION', ({ usecase, payload }) => {
const nextStatus = usecase.reduce(status, payload);
assert.deepEqual(nextStatus, { counter: { count: 10 } });
assert.deepEqual(payload, { count: 10 });
assert(usecase.id === "INCREMENT");
});
Then dispatch payload for update status.
dispatch(increment(10))
import assert from 'assert';
import { UseCase, Dispatcher } from 'alx';
/* usecase */
const { comppse } = UseCase.initialize();
const increment = compose((u) => {
u.id = "INCREMENT";
u.action = (count = 1) => ({ count });
u.reducer = (status, payload) => ({
counter: { count: status.counter.count + payload.count }
});
})
/* Dispatcher */
const dispatcher = new Dispatcher();
const dispatch = dispatcher.dispatch.bind(dispatcher);
/* initial status */
let status = {
counter: { count: 0 }
};
/* listen dispatcher */
dispatcher.subscribe('USECASE:ACTION', ({ usecase, payload }) => {
const nextStatus = usecase.reduce(status, payload);
assert.deepEqual(status, { counter: { count: 10 } });
assert.deepEqual(payload, { count: 10 });
assert(usecase.id === "INCREMENT");
});
disptcher.subscribe('ERROR', (error) => {
console.error(error)
});
/* emit usecase */
disptch(increment(1));
You can use reducers
property. usecase.reducer
must return complete status. But usecase.reducers
have only to return own context state.
let status = {
counter: { count: 0 }
};
const increment = compose('INCREMENT', {
action: (count = 1) => ({ count }),
/*
reducer: (status, payload) => ({
counter: { count: status.counter.count + payload.count }
});
*/
reducers: {
counter: (state, payload) => ({
count: state.count + payload.count
})
}
})
action
can return Promise directly.
const increment = compose((u) => {
u.id = "INCREMENT";
u.action = (count = 1) => Promise.resolve({ count });
u.reducer = (status, payload) => ({
counter: { count: status.counter.count + payload.count }
});
})
Create usecase and chain function. Chain
is next process depend on the usecase.
Chain
can use GeneratorFunction
. I recommend to use GeneratorFunciton
. It let debugging to easy.
/* usecase */
const reset = compose((u) => {
u.id = "RESET";
u.reducer = () => ({ counter: { count: 0 } });
});
const resetChain = function* resetChain({ getStatus, dispatch }) {
const { count } = getStatus("counter");
if (count > 100 || count < -100) {
yield dispatch(reset());
}
};
Apply chain. usecase.chain
accept Array of Function or Function.
// chain property
const increment = compose((u) => {
u.id = "INCREMENT";
...
u.chain = resetChain // or [resetChain]
})
The same as above, link()
helper that initialized by UseCase can add chain to the usecase.chain
. It also accept multiple chain function like link(increment, chain, chain)
.
const { link } = UseCase.initialize();
const incrementWithReset = link(increment, resetChain);
UseCase#next
implements chain. Chain functions are implemented asynchronous.
// UseCase#next
dispatcher.subscribe('USECASE:ACTION', ({ usecase, payload }) => {
usecase.next(payload, getStatus, emit);
});
Alx does not provide store
. So you need to implement suitably getStatus
function.
const usecase = compose((u) => {
u.action = () => {...}
})
UseCaseId can omit. When omit id , usecase.id is undefined
by default.
If you dont need id, you omit id.
const usecase = compose((u) => {
u.id = "SUMETHING";
u.reducer = () => {...}
})
When omit action
, default action is setted automaticaly. default action will return undefined
as payload.
action
can omit, if all reducer(s) dont need payload or action do not have a specific value.
const usecase = compose((u) => {
u.id = "SUMETHING";
u.action = () => {...}
})
When omit reducer(s)
, payload and usecaes is published but status will not change.
This pattern is useful when You want only to link chain or want to publish event that dont change state for something.