Экология React, чтение исходного кода dva

React.js Redux

Идея   dva по-прежнему очень хороша, что значительно повышает эффективность разработки. dva интегрирует Redux и промежуточное ПО Redux Redux-saga, React-router и так далее. Благодаря управлению состоянием Redux и концепции асинхронной обработки с помощью задач и эффектов в Redux-saga, dva сильно инкапсулирована на основе этих инструментов и предоставляет только несколько простых API для проектирования моделей данных.

Недавно я просмотрел исходный код Redux-saga в сочетании с темным режимом redux, который использовался в проекте для разделения редюсеров и саг (асинхронная обработка функции генератора) на разные подстраницы и один и тот же файл на каждой странице. Он содержит редьюсер и сагу состояния страницы.Эта простая инкапсуляция может значительно улучшить читаемость проекта.

   Недавно я прочитал исходный код dva и познакомился с тем, как dva инкапсулируется в верхний уровень. Следующее будет от мелкого к глубокому, слегка в процессе чтения исходного кода dva их собственного понимания.

  • редукс-темный режим
  • Использование и понимание исходного кода версии dva 0.0.12

Оригинальный адрес этой статьи:GitHub.com/fort и все…приветственная звезда


1. Redux-темный режим

   При использовании redux и redux-saga, особенно в том, как хранить функцию редуктора и функцию генератора saga, эти две функции напрямую связаны с тем, как обрабатывать данные.

   После использования саги об асинхронном промежуточном программном обеспечении в избыточности в избыточности просмотреть полный поток данных и информации:

default

При наличии асинхронной логики действие простого объекта выдается в компоненте пользовательского интерфейса, а затем обрабатывается промежуточным программным обеспечением redux-saga, redux-saga передает это действие в соответствующий канал через метод эффекта redux- саги (такие как call, put, apply и т. д.) для создания объекта описания, а затем преобразовать этот объект описания в функцию с побочными эффектами и выполнить ее.

Когда redux-saga выполняет функцию с побочными эффектами, она может отправить действие, которое также является простым объектом, который будет напрямую передан функции редюсера для обработки, то есть действию, выданному в задаче редукса. -saga — это синхронное действие.

Краткое изложение:Действия, отправляемые из компонентов пользовательского интерфейса, проходят два уровня обработки, а именно промежуточное ПО redux-saga и функции редукции redux.

Режим   redux-dark очень прост: поместить redux-saga на ту же подстраницу для обработки функции саги действий, а редуктор — для обработки состояния на подстранице.Функция редьюсера находится в том же файле.

Например:

 import { connect } from 'react-redux';
 class Hello extends React.Component{
     componentDidMount(){
       //发出一个action,获取异步数据
       this.props.dispatch({
          type:'async_count'
       })
     }
     
 
 }
 export default connect({})(Hello);
 

Чтобы создать действие типа = 'async_count' из компонента Hello, мы используем шаблон redux-dark, чтобы поместить функции саги и редуктора в один и тот же файл:

    import { takeEvery } from 'redux-saga/effects';
    
    //saga
    function * asyncCount(){
      console.log('执行了saga异步...')
      //发出一个原始的action
      yield put({
        type:'async'
      });
    }
    function * helloSaga(){
        //接受从UI组件发出的action
        takeEvery('async_count',asyncCount);
    }
    
    //reducer
    function helloReducer(state,action){
       if(action.type === 'count');
       return { ...state,count + 1}
    }

Выше приведен пример размещения саги и редуктора в одном файле. Режим Redux-Dark для организации кода может быть более интуитивным и унифицировать уровень обработки данных. После разделения подстраниц каждая подстраница соответствует файлу. Очень читабельно.

Во-вторых, использование и понимание исходного кода версии dva 0.0.12.

   Приведенный выше режим redux-dark — это простой процесс, а dva — это дальнейшая инкапсуляция.Dva инкапсулирует не только redux и redux-saga, но и react-router-redux, react-router и так далее. Это позволяет нам использовать redux, redux-saga, react-router и т. д. через очень простую конфигурацию.

Давайте сначала возьмем начальную версию dva в качестве примера, чтобы понять исходный код dva.

(1), использование два 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) {
      //..处理监听等等函数
    }
  ],
  
});

// 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'));

Всего три шага, чтобы завершить пример, как иметь дело с действием, мы используем диаграмму для представления:

default

То есть, чтобы выполнить действие типа объекта, выданного компонентом пользовательского интерфейса, сначала сопоставьте тип действия в свойстве эффектов в соответствии с типом при инициализации =model.

  • Если в свойствах эффектов есть соответствующая функция обработки типа действия, то сначала выполняется соответствующая функция в эффектах, и действие может быть выдано дважды при выполнении этой функции, а второе выданное действие будет напрямую передано в редюсер функция.
  • Если в свойствах эффектов нет функции обработки соответствующего типа действия, он будет напрямую искать функцию обработки соответствующего типа из редуктора.

Функция в атрибуте Effects в процессе инициализации dva на самом деле является функцией saga в redux-saga, в которой обрабатывается прямая асинхронная логика, и функция может дважды выдавать синхронные действия.

Кроме того, dva также может инициализировать маршрутизацию через метод маршрутизатора.

(2), прочитайте исходный код dva 0.0.12

Давайте непосредственно прочитаем исходный код dva 0.0.12.Следующий код является исходным кодом dva после моей оптимизации:

//Provider全局注入store
import { Provider } from 'react-redux';
//redux相关的api
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
//redux-saga相关的api,takeEvery和takeLatest监听等等
import createSagaMiddleware, { takeEvery, takeLatest } from 'redux-saga';
//react-router相关的api
import { hashHistory, Router } from 'react-router';
//在react-router4.0之后已经较少使用,将路由的状态存储在store中
import { routerMiddleware, syncHistoryWithStore, routerReducer as routing } from 'react-router-redux';
//redux-actions的api,可以以函数式描述reducer等
import { handleActions } from 'redux-actions';
//redux-saga非阻塞调用effect
import { fork } from 'redux-saga/effects';

function dva() {
  let _routes = null;
  const _models = [];
  //new dva暴露了3个方法
  const app = {
    model,
    router,
    start,
  };
  return app;
  //添加models,一个model对象包含了effects,reducers,subscriptions监听器等等
  function model(model) {
    _models.push(model);
  }
  //添加路由
  function router(routes) {
    _routes = routes;
  }

  
  function start(container) {

    let sagas = {};
    //routing是react-router-redux的routerReducer别名,用于扩展reducer,这样以后扩展后的reducer就可以处理路由变化。
    let reducers = {
      routing
    };
    _models.forEach(model => {
      //对于每一个model,提取其中的reducers和effects,其中reducers用于扩展redux的reducers函数,而effects用于扩展redx-saga的saga处理函数。
      reducers[model.namespace] = handleActions(model.reducers || {}, model.state);
      //扩展saga处理函数,sagas是包含了所有的saga处理函数的对象
      sagas = { ...sagas, ...model.effects }; ---------------------------(1)
    });

    reducers = { ...reducers };
    
    //获取决定使用React-router中的那一个api
    const _history = opts.history || hashHistory;
    //初始化redux-saga
    const sagaMiddleware = createSagaMiddleware();
    //为redux添加中间件,这里添加了处理路由的中间件,以及redux-saga中间件。
    const enhancer = compose(
      applyMiddleware.apply(null, [ routerMiddleware(_history), sagaMiddleware ]),
      window.devToolsExtension ? window.devToolsExtension() : f => f
    );
    const initialState = opts.initialState || {};
    //通过combineReducers来扩展reducers,同时生成扩展后的store实例
    const store = app.store = createStore(
      combineReducers(reducers), initialState, enhancer
    );

    // 执行model中的监听函数,监听函数中传入store.dispatch
    _models.forEach(({ subscriptions }) => {
      if (subscriptions) {
        subscriptions.forEach(sub => {
         store.dispatch, onErrorWrapper);
        });
      }
    });
    
     // 根据rootSaga来启动saga,rootSaga就是redux-saga运行的主task
    sagaMiddleware.run(rootSaga);
    
    
    //创建history实例子,可以监听store中的state的变化。
    let history;
    history = syncHistoryWithStore(_history, store); --------------------------------(2)
    

    // Render and hmr.
    if (container) {
      render();
      apply('onHmr')(render);
    } else {
      const Routes = _routes;
      return () => (
        <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;

Чтение кода дается в виде взгляда, и стоит отметить три основных момента:

  • В примечании (1) handleActions — это API, инкапсулированный redux-actions для упрощения написания функций-редюсеров. Вот пример handleActions:
const reducer = handleActions(
  {
    INCREMENT: (state, action) => ({
      counter: state.counter + action.payload
    }),
​
    DECREMENT: (state, action) => ({
      counter: state.counter - action.payload
    })
  },
  { counter: 0 }
);

Функции атрибутов INCREMENT и DECREMENT могут обрабатываться отдельно, а действие типа = "INCREMENT" и type = "DECREMENT".

  • В комментарии (2) через API react-router-redux syncHistoryWithStore может расширить историю, чтобы история могла отслеживать изменения хранилища.

  • В примечании (3) это rootSaga, который является основной задачей при запуске redux-saga, В этой задаче мы определяем ее следующим образом:

function* rootSaga() {
  for (let k in sagas) {
    if (sagas.hasOwnProperty(k)) {
      const watcher = getWatcher(k, sagas[k]);
      yield fork(watcher);
    }                     
  }
}

Из глобального объекта саги, который содержит все функции саги, получите соответствующие свойства и разветвите соответствующие слушатели.Обычно используемые слушатели здесь — это два API редукс-саги, такие как takeEvery и takeLatest.

Резюме: Выше приведен исходный код самой ранней версии dva, которая очень кратко использует redux, redux-saga, react-router, redux-actions, react-router-redux и т. д. Его цель также очень проста:

Упростите утомительную логику экологии, связанной с избыточностью

Адрес источника справки:GitHub.com/Но VA Technologies/DV Ah/He…