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
  1. 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