Dva has a good idea and greatly improves development efficiency. Dva integrates Redux, Redux-Saga, react-Router, and so on. Thanks to Redux’s state management and redux-Saga’s concept of handling asyncrony through tasks and effects, DVA is highly encapsulated on top of these tools, exposing only a few simple apis to design data models.

Having looked at the source code of Redux-Saga recently, combined with the redux-Dark mode used in the previous project to split reducers and SagAs (generator functions to handle async) into different sub-pages, The same file in each page contains reducer and saga of the page state. Such simple encapsulation has greatly improved the readability of the project.

Recently read dVA source code, familiar with DVA is how to do packaging in the upper layer. Below will be from shallow to deep, light in the process of reading DVA source their own understanding.

  • Story – dark mode
  • Dva 0.0.12 version of the use and source understanding

The original address of this article is github.com/fortheallli… Welcome to star


Redux-dark mode

When using REdux and Redux-Saga, specifically how to store the Reducer function and the Saga generator function, which are directly related to how to process data.

To review the complete flow of data and information following the use of asynchronous middleware Redux-Saga in Redux:

With asynchronous logic, issue a plain Object action in the UI Component, which is processed by the Redux-Saga middleware. Redux-saga passes this action to the appropriate channel, generates a description object through redux-Saga’s effect methods (such as call, PUT, apply, etc.), and then converts this description object into a function with side effects and executes it.

When redux-saga executes functions with side effects, an action can be dispatched. This action is also a plain object and is passed directly into the Reducer function of Redux for processing. That is, the actions issued in the Redux-Saga task are synchronized actions.

Simple summary: The actions issued from the UI component are processed in two layers, the Redux-Saga middleware and the Reducer function of Redux.

The redux-dark mode is very simple. It is to put the Saga function that redux-saga processes actions under the same subpage and the Reducer function that reducer processes state under the subpage into the same file.

For example:

 import { connect } from 'react-redux';
 class Hello extends React.Component{
     componentDidMount(){
       // Issue an action to get asynchronous data
       this.props.dispatch({
          type:'async_count'}}})export default connect({})(Hello);
 
Copy the code

Issue an action of type = ‘async_count’ from the Hello component. We use redux-dark mode to put the saga and reducer functions in the same file:

    import { takeEvery } from 'redux-saga/effects';
    
    //saga
    function * asyncCount(){
      console.log('Execute saga async... ')
      // Issue a raw action
      yield put({
        type:'async'
      });
    }
    function * helloSaga(){
        // Accept the action emitted from the UI component
        takeEvery('async_count',asyncCount);
    }
    
    //reducer
    function helloReducer(state,action){
       if(action.type === 'count');
       return { ...state,count + 1}}Copy the code

This is an example of putting saga and Reducer in the same file. Redux-dark mode to organize the code, can be more intuitive, unified data processing layer. After subpages are split, each subpage corresponds to a file. Very readable.

Dva 0.0.12 version of the use and understanding of the source code

The redux-dark mode mentioned above is a simple process, while dVA is a further step of encapsulation. Dva not only encapsulates Redux and Redux-Saga, but also react-router-Redux and React-Router, etc. This allows us to use redux, redux-Saga, React-Router, etc., with very simple configuration.

First of all, take the initial version of DVA as an example to understand dVA source code.

(1) The use of DVA 0.0.12

Dva 0.0.12 dVA 0.0.12

// 1. Initialize
const app = dva();

// 2. Model
app.model({
  namespace: 'count'.state: 0.effects: {['count/add'] :function* () {
      console.log('count/add');
      yield call(delay, 1000);
      yield put({
        type: 'count/minus'}); }},reducers: {['count/add'  ](count) { return count + 1},'count/minus'](count) { return count - 1}},subscriptions: [
    function(dispatch) {
      / /.. Handles listener and so on}]});// 3. View
const App = connect(({ count }) = > ({
  count
}))(function(props) {
  return (
    <div>
      <h2>{ props.count }</h2>
      <button key="add" onClick={()= > { props.dispatch({type: 'count/add'})}}>+</button>
      <button key="minus" onClick={()= > { props.dispatch({type: 'count/minus'})}}>-</button>
    </div>
  );
});

// 4. Router
app.router(({ history }) = >
  <Router history={history}>
    <Route path="/" component={App} />
  </Router>
);

// 5. Start
app.start(document.getElementById('root'));
Copy the code

It only takes three steps to complete an example. How to handle an action is represented by a graph:

The action of the object type emitted on the UI component matches the action Type in the Effects property =model when initialized.

  • If there is a corresponding action type handler in the Effects property, execute the corresponding function in the Effects first. In executing this function, you can issue secondary actions, which are directly passed into the Reducer function.
  • If there is no corresponding action type handler in the Effects attribute, the reducer will be directly searched for the corresponding type handler.

The function in the Effects property during dVA initialization is actually the Saga function in Redux-Saga, which handles direct asynchronous logic and can issue synchronous actions twice.

In addition, DVA can initialize routes through the Router method.

(2), DVA 0.0.12 source code reading

Dva 0.0.12 dVA 0.0.12 dVA 0.0.12

//Provider globally injects the store
import { Provider } from 'react-redux';
// Redux-related API
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
// Redux-saga related apis,takeEvery and takeLatest listening etc
import createSagaMiddleware, { takeEvery, takeLatest } from 'redux-saga';
// React-router API
import { hashHistory, Router } from 'react-router';
// The route state is stored in the store
import { routerMiddleware, syncHistoryWithStore, routerReducer as routing } from 'react-router-redux';
// Redux-Actions API, which can be used to describe reducer, etc
import { handleActions } from 'redux-actions';
//redux-saga non-blocking call effect
import { fork } from 'redux-saga/effects';

function dva() {
  let _routes = null;
  const _models = [];
  // New DVA exposes 3 methods
  const app = {
    model,
    router,
    start,
  };
  return app;
  / / add models, a model object contains the effects, reducers, subscriptions listener and so on
  function model(model) {
    _models.push(model);
  }
  // Add a route
  function router(routes) {
    _routes = routes;
  }

  
  function start(container) {

    let sagas = {};
    // Routing is the routerReducer alias of react-router-redux. It is used to expand the reducer so that the expanded reducer can handle route changes later.
    let reducers = {
      routing
    };
    _models.forEach(model= > {
      // For each model, extract the reducers and effects, in which reducers is used to extend the Reducers function of Redux, and effects is used to extend the Saga handler function of RedX-Saga.
      reducers[model.namespace] = handleActions(model.reducers || {}, model.state);
      // Extend the saga handler, sagas is the object that contains all the saga handlerssagas = { ... sagas, ... model.effects }; -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- (1)
    });

    reducers = { ...reducers };
    
    // Get the API that decides to use the react-router
    const _history = opts.history || hashHistory;
    // Initialize redux-saga
    const sagaMiddleware = createSagaMiddleware();
    // Add middleware for Redux, which adds middleware to handle routing, and redux-Saga middleware.
    const enhancer = compose(
      applyMiddleware.apply(null, [ routerMiddleware(_history), sagaMiddleware ]),
      window.devToolsExtension ? window.devToolsExtension() : f= > f
    );
    const initialState = opts.initialState || {};
    // Use combineReducers to extend reducers and generate the extended Store instance
    const store = app.store = createStore(
      combineReducers(reducers), initialState, enhancer
    );

    // Execute the listener in model, passing in store.dispatch
    _models.forEach(({ subscriptions }) = > {
      if (subscriptions) {
        subscriptions.forEach(sub= >{ store.dispatch, onErrorWrapper); }); }});// Start saga according to rootSaga, which is the main task that Redux-Saga runs
    sagaMiddleware.run(rootSaga);
    
    
    // Create a history example to listen for state changes in the store.
    lethistory; history = syncHistoryWithStore(_history, store); -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- (2)
    

    // Render and hmr.
    if (container) {
      render();
      apply('onHmr')(render);
    } else {
      const Routes = _routes;
      return (a)= >( <Provider store={store}> <Routes history={history} /> </Provider> ); } function getWatcher(k, saga) { let _saga = saga; let _type = 'takeEvery'; if (Array.isArray(saga)) { [ _saga, opts ] = saga; _type = opts.type; } function* sagaWithErrorCatch(... arg) { try { yield _saga(... arg); } catch (e) { onError(e); } } if (_type === 'watcher') { return sagaWithErrorCatch; } else if (_type === 'takeEvery') { return function*() { yield takeEvery(k, sagaWithErrorCatch); }; } else { return function*() { yield takeLatest(k, sagaWithErrorCatch); }; } } function* rootSaga() { for (let k in sagas) { if (sagas.hasOwnProperty(k)) { const watcher = getWatcher(k, sagas[k]); yield fork(watcher); } -----------------------------(3) } } function render(routes) { const Routes = routes || _routes; ReactDOM.render(( <Provider store={store}> <Routes history={history} /> </Provider> ), container); } } } export default dva;Copy the code

The reading of the code above is given in the form of a gaze. There are three main points worth noting:

  • In comment (1), handleActions is an API encapsulated by Redux-Actions to simplify writing the Reducer function. Here is an example of handleActions:
const reducer = handleActions(
  {
    INCREMENT: (state, action) = > ({
      counter: state.counter + action.payload
    }),
​
    DECREMENT: (state, action) = > ({
      counter: state.counter - action.payload
    })
  },
  { counter: 0});Copy the code

Functions for the INCREMENT and DECREMENT properties can be handled separately, with actions for type = “INCREMENT” and Type = “DECREMENT”.

  • In comment (2), syncHistoryWithStore extends history through the react-router-redux API so that history can listen for store changes.

  • In comment (3) is a rootSaga, which is the main Task of redux-Saga when it is running. In this Task we define it as follows:

function* rootSaga() {
  for (let k in sagas) {
    if (sagas.hasOwnProperty(k)) {
      const watcher = getWatcher(k, sagas[k]);
      yieldfork(watcher); }}}Copy the code

Get the corresponding properties from the global Sagas object that contains all the Saga functions, and fork the corresponding listener. The listener commonly used here is the two Redux-Saga apis such as takeEvery and takeLatest.

Redux, Redux-Saga, redux-Router, Redux-Actions, redux-router-Redux, redux-router-redux, redux-Router-redux The purpose is simple:

Simplify the cumbersome logic of redux-related ecology

Reference source code address: github.com/dvajs/dva/t…