The dVA, NUOMI, Rematch, and Mirror data flow management modes are implemented
-
To realize the function of redux native writing method, a large number of action and Reducer template codes need to be written. For complex projects, there are too many template codes, and action files and reducer files keep switching, resulting in poor development experience. People tend to encapsulate and simplify.
-
For example, the implementation of DVA introduces effects and puts state, effects, action and Reducer into a model file, simplifying the definition of action and reducer. Rematch,mirror.
-
In this year’s project reconstruction, due to routing cache and other reasons, the existing DVA and other frameworks were not adopted, and the REDUx data flow similar to DVA was realized by simple encapsulation.
Simple encapsulation implementation under similar API and usage.
The implementation code
- Name zoo because the z letter is at the bottom of the list of files for easy lookup
Directly on the code, redux main function encapsulation
import { createStore } from 'redux';
import { Provider } from 'react-redux';
// Add a namespace to Effects or reducer to save globally
const addNamespace = (obj, name) = > {
const newObj = {};
Object.keys(obj).forEach(item= > {
newObj[`${name}/${item}`] = obj[item];
});
return newObj;
};
class Zoo {
constructor() {
// Define public state, store, effects, etc
this.state = {};
this.models = {};
this.reducers = {};
this.effects = {};
this.store = {};
}
// the zoo initialization method is passed to each module's model
init(models) {
Object.values(models).forEach(item= > {
// Iterate over and load each model
this.model(item);
});
// Create and return the global store
return this.createStore();
}
// Load the module model method
model(modelObj) {
const { state, reducer, effects, namespace } = modelObj;
// Save state globally
this.state[namespace] = state;
this.models[namespace] = modelObj;
/ / save the reducer
const newReducer = addNamespace(reducer, namespace);
this.reducers[namespace] = newReducer;
/ / save the effects
this.effects[namespace] = effects;
}
createStore() {
// Merge the reducer and create the reducer function
const reducer = (state = this.state, action) = > {
let newState = state;
const { type, payload } = action;
// Get the namespace for each action
const [namespace, typeName] = type.split('/');
// Obtain state and reducer function objects in the corresponding model based on the namespace
const currentState = newState[namespace];
const currentReducer = this.reducers[namespace];
If action corresponds to reducer, modify state according to the reducer function; otherwise, return the original state directly
if (currentReducer && currentReducer[type] && currentState) {
Modify the state of the current namespace according to the reducer function
newState[namespace] = currentReducer[type](payload, currentState);
// The modified state must be a new object so that the old state is not overwritten and the changes can take effectnewState = { ... newState }; }return newState;
};
// Call redux createStore to create a store
this.store = createStore(reducer);
const { dispatch, getState } = this.store;
/** * Add global Store dispatch and getState methods to each Model's Effects object * to call Dispatch in Effects * and add method names in Effects Namespace, used to distinguish modules */ during dispatch in components
Object.keys(this.effects).forEach(namespace= > {
this.effects[namespace].dispatch = ({ type, payload }) = >
// Modify action type to add a namespace
dispatch({ type: `${namespace}/${type}`, payload });
this.effects[namespace].getState = getState;
});
return this.store; }}export default new Zoo();
Copy the code
- Connect to encapsulate
import React from 'react';
import { connect } from 'react-redux';
import zoo from './zoo';
// effectsArr can be used as effects dependency injection
export default (mapState, mapDispatch = {}, effectsArr = []) => {
return Component= > {
const { getState, dispatch } = zoo.store;
// By default, the corresponding method in Effects is triggered first. If it does not exist, it is used as normal Action Dispatch
const myDispatch = ({ type, payload }) = > {
const [typeId, typeName] = type.split('/');
const { effects } = zoo;
if (effects[typeId] && effects[typeId][typeName]) {
return effects[typeId][typeName](payload);
}
dispatch({ type, payload });
};
const NewComponent = props= > {
const { effects } = zoo;
const effectsProps = {};
// Add an effects object to the component to make it easier to call methods in Effects
effectsArr.forEach(item= > {
if (effects[item]) {
effectsProps[`${item}Effects`] = effects[item];
myDispatch[`${item}Effects`] = effects[item]; }});return <Component {. props} dispatch={myDispatch} {. effectsProps} / >;
};
return connect(mapState, mapDispatch)(NewComponent);
};
};
Copy the code
As mentioned above, the wrapped Connect extends a lot of functionality. Instead of just triggering actions, the dispatches obtained from components call the methods in Effects directly, making it easier to handle side effects. Effects dependency injection interface (like Inject in Mobx) is also added.
The zoo implementation is complete and the store created by Zoo is no different from the store created by Redux natively.
Zoo with
- index.js
import { Provider } from 'react-redux';
import zoo from './zoo';
import todoModel from './zooExample/Todo/model';
import zooModel from './zooExample/Zoo/model';
import ZooExample from './zooExample/index';
// Just pass in each module model
const zooStore = zoo.init({
todoModel,
zooModel
});
render(
<Provider store={zooStore}>
<ZooExample />
</Provider>.document.getElementById('root'));Copy the code
- model.js
export default {
namespace: 'zoo'.state: {
list: []},effects: {
setState(payload) {
const state = this.getState();
this.dispatch({ type: 'setState'.payload: payload });
},
addAnimal(name) {
const { list } = this.getState().zoo;
this.setState({ list: [...list, name] });
},
async deleteOne() {
const { list } = this.getState().zoo;
const res = [...list];
// Simulate an asynchronous request operation
setTimeout((a)= > {
res.pop();
this.setState({ list: res });
}, 1000); }},reducer: {
setState: (payload, state) = >({... state, ... payload }) } };Copy the code
- Function component zooexample.js
import React, { useState, useEffect } from 'react';
import { connect } from '.. /.. /zoo';
const TestTodo = ({ dispatch, list, zooEffects }) = > {
const [value, setValue] = useState(' ');
useEffect((a)= > {
dispatch({ type: 'zoo/getAnimal'}); } []);const onAdd = (a)= > {
dispatch({
type: 'zoo/addAnimal'.payload: value
});
};
const onDelete = (a)= > {
zooEffects.deleteOne();
/ / or dispatch. ZooEffects. DeleteOne ();
};
return (
<div>
<input onChange={e= > setValue(e.target.value)} />
<button onClick={onAdd}>add animal</button>
<button onClick={onDelete}>delete animal</button>
<br />
<ul>
{list.map((item, i) => {
return <li key={item + i} >{item}</li>;
})}
</ul>
</div>); }; export default connect( state => { return { list: state.zoo.list }; }, {}, // effects injected ['todo', 'zoo'])(TestTodo);Copy the code
A simple REdux package is completed, about 100 lines of code, no need to write action, no need to write switch case, compared with DVA asynchronous request is simple, dispatch function is powerful, can realize three ways to trigger effects can also be very simple.
The packaging principle of Redux in Nuomi is basically the same
The above code works fine, but the package of the CONNECT method has some details like REF penetration
Sample code repository github.com/iblq/zoo