Asynchronous stream development util. It's more convenient, modulized and typesafe to develop redux style application with AXR.
AXR takes the first character from each role of redux:
- A: Redux action
- X: Asyncchronous stream lib, maybe saga or thunk
- R: Redux reducer
AXR is based on redux-saga . redux is not the required, you still can use AXR will your own state management style.
Use npm:
npm install axr
or use yarn:
yarn add axr
AXR dependents on two apis: dispatch and getState, so you need to set them first:
// in setup.js // 1: import axrSetOptions(), axrSetOptions will tell what axr needed import { axrSetOptions } from 'axr/dist/ASR'; // 2: Setup the two api, just delegate to redux store axrSetOptions({ getState: () => { return store.getState(); }, dispatch: (action) => { return store.dispatch(action); } }); // 3: Now, export anything from axr export * from 'axr/dist/ASR';
After setup options, just import apis from setup.js to create an AXR module. NB: Don't import apis from AXR directly.
// in AXR.js import { actionCreator, sagaCreator, reducerCreator, axr } from './setup.js'; // Create an action const appStart = actionCreator<string>('appStart'); // Create an saga const sagaAppStart = sagaCreator(appStart, function*(payload, getState) { console.log(payload); }); // Create a reducer const startInfo = reducerCreator('Hello World', appStart, (state, payload) => { return payload; }) // Export axr export default axr( { appStart, }, [ sagaAppStart, ], { startInfo, });
Now setup saga and redux in application!
import AXR from './AXR'; // Create redux sagaMiddleware const sagaMiddleware = createSagaMiddleware(); // Create root reducer const rootReducer = combineReducers(AXR.reducer); // Create store const store = createStore(rootReducer, applyMiddleware(sagaMiddleware)); // Start saga const sagas = AXR.handler; const rootSaga = function*() { yield all(sagas.map(saga => spawn(saga.saga))); }; sagaMiddleware.run(rootSaga);
Trigger an action:
import AXR from './AXR'; // Trigger the appStart action AXR.action.appStart.dispatch('Hello AXR');
This is all of AXR.
Create an actionCreator with prefix.
// Create an actionCreator with prefix 'BZ_A' const actionCreator = actionCreatorFactory('BZ_A');
Create an action object, the type of action object is type. If the actionCreator is from actionCreatorFactory(prefix), the type will automatically prefixed.
The action object is a function in fact, and has some properties as bellow:
- type: The type of action object (prefixed)
- match(type): Check action type
- dispatch(payload): Trigger an action with payload data
Example:
// Create an action object const appStart = actionCreator('appStart'); // Create an action // action.type === 'appStart' // action.payload === undefined const action = appStart(); // Check action is 'appStart' and do something if (appStart.match('appStart')) { // Do something } // Trigger an action appStart.dispatch(); //////////////////////////////////////////////////// // Create an action with payload // The type of payload is // { // username: string, // password: string, // } const login = actionCreator< { username: string, password: string, }>('login'); // Create an action data // action.type === 'login' // action.payload.username === 'zhangsan' // action.payload.password === 'admin@123' const action = login({ username: 'zhangsan', password: 'admin@123', }); // Trigger an action login.dispatch({ username: 'zhangsan', password: 'admin@123', });
Create an async action object. An async action object has three sub action objects: started, done and failed. The payload of done is:
{ // The param of async action params: startPayload, // The result of async action result: doneResult, }
Example:
// Create an async action with started payload and done payload // The payload type of started action is // { // username: string, // password: string, // } // // The payload type of done action is // { // name: string, // age: number, // } const login = actionCreator< { username: string, password: string, }, { name: string, age: number, }>('login'); // Trigger an started action login.started.dispatch({ username: 'zhangsan', password: 'admin@123', }); // Trigger an done action login.done.dispatch({ params: payload, result: result, });
Create an saga, the saga will be actived while action trigged. The action is an action object created by actionCreator, the handle is a generaor.
The defination of handle is:
function*(payload, getState, action){}
- payload: The data of triggered action
- getState: State getter
- action: The triggered action
There are some default saga helpers from redux-saga.
- latest(action, handle):Wrapped
takeLatest - every(action, handle):Wrapped
takeEvenry - throttle(action, time, handle):Wrapped
throttle
const sagaFromLatest = sagaCreator(action, handle); const sagaFromEvery = sagaCreator.evenry(action, handle); const sagaFromThrottle = sagaCreator.throttle(action, 1000, handle);
The result of sagaCreator (or helpers) has the property as bellow:
- saga: The actual saga
- handle: The raw generator from arguments
Example:
// Create an action const appStart = actionCreator<string>('appStart'); // Create saga handler of action const sagaAppStart = sagaCreator(appStart, function*(payload, getState) { // Do the async task const result0 = yield ...; // Do the async task const result1 = yield ...; // Do something else ... }); // Or an async action const login = actionCreator< { username: string, password: string, }, { name: string, age: number, }>('login'); const sagaLogin = sagaCreator(login.started, function*(payload, getState) { // loginAPI will use username and password // and return a promise const result = yield loginAPI(payload); // Trigger the done event login.done.dispatch({ params: payload, result: result, }); })
Create a reducer. The arguments as bellow:
- initState: Initial state of this reducer
- action: The related action object
- reducer: Reducer handle, has the defination
(state, payload, action) => state- state: The old state
- payload: Data with the triggered action
- action: The triggered action
Example:
// Create a reducer will be called when login.done triggered. // The reducer just return the payload of this action. const userInfo = reducerCreator({ name: '', age: 0}, login.done, (state, payload, action) => { return payload.result; });
Create a reducer who can handle mutiple action types. The result can use case(action, handle) to declare action handle branch.
Example:
// Create a reducer will be called when login.done or login.started triggered. const userInfo = reducersCreator({ name: '', age: 0}) .case(login.started, (state, payload, action) => { // Reset state when started action return { name: '', age: 0, }; }) .case(login.done, (state, payload, action) => { // Record the data when done action return payload.result; });
Create a axr object. An axr object has these information:
- action: A
map, include the whole action objects - handler: An
array, include the whole sagas - reducer: A
map, include the whole reducers
The key of action map is what used to dispatch action.
axrObject.action.appStart() // or axrObject.action.appStart.dispatch();
The key of reducer map is the property name in global state of store.
Example:
export default axr( { // Use axr.action.appStart and axr.action.login appStart, login, }, [ sagaAppStart, sagaLogin, ], { // Use state.userInfo to get the data userInfo, } );
Combine mutiple axr objects as a big axr object. If there is name conflict in action or reducer, will throw an error.
axrCombine is the method to modulize our AXR source code.
// The directories
// The AXR in root will combine the sub directories's AXR (a and b)
root
AXR
export axrCombine(aAXR, bAXR...)
a
AXR
export axr()
b
AXR
export axr()
...
Example:
export default axrCombine( commonAXR, loginAXR, ... );
Setup AXR, the options must provide:
- dispatch: Action dispatch function, the same as redux
dispatch(action) => action - getState: State getter function, the same as redux
getState() => state
Example:
axrSetOptions({ getState: () => { return app().axrState(); }, dispatch: (action) => { return app().axrDispatch(action); } });