Dva: DVA源码阅读-初始化篇

Created on 2 Apr 2017  ·  1Comment  ·  Source: dvajs/dva

原文参见:https://github.com/jnotnull/dva-generator/issues/1

DVA是一个优秀的框架,它很好的集成了Redux和Saga,极大的方便了开发者的异步处理,为我们快速开发提供可能。为什么说DVA是一个框架,而不是库呢。下面我们从源码中给出答案。

在调用 const app = dva(); 初始化之后,dva为我们提供了三个参数入口,分别为app.model、app.router、app.start。那我们就先看下这个最核心部分的初始化过程。

在createDva文件中的dva方法下的如下部分:

const app = {
  // properties
  _models: [],
  _router: null,
  _store: null,
  _history: null,
  _plugin: plugin,
  _getProvider: null,
  // methods
  use,
  model,
  router,
  start,
};
return app;

properties我们先不看,先看下methods。use是插件相关的也先不管,重点看model、router和start三个方法。

  1. model和router也很简单,就是分别在_models数组中注入model以及初始化_router

    function model(model) {
      this._models.push(checkModel(model, mobile));
    }
    
    function router(router) {
      invariant(typeof router === 'function', 'app.router: router should be function');
      this._router = router;
    }
    
  2. 现在重点看下start方法

    const onError = plugin.apply('onError', (err) => {
        throw new Error(err.stack || err);
      });
      const onErrorWrapper = (err) => {
        if (err) {
          if (typeof err === 'string') err = new Error(err);
          onError(err, app._store.dispatch);
        }
      };
    

    这里的重要功能是在onError中注入dispatch,这样在捕获异常后能够继续执行dispatch。

    for (const m of this._models) {
        reducers[m.namespace] = getReducer(m.reducers, m.state);
        if (m.effects) sagas.push(getSaga(m.effects, m, onErrorWrapper));
      }
    

    遍历model,初始化reducers和sagas。这里比较重要的是getSaga函数:

    function getSaga(effects, model, onError) {
      return function *() {
        for (const key in effects) {
          if (Object.prototype.hasOwnProperty.call(effects, key)) {
            const watcher = getWatcher(key, effects[key], model, onError);
            const task = yield sagaEffects.fork(watcher);
            yield sagaEffects.fork(function *() {
              yield sagaEffects.take(`${model.namespace}/@@CANCEL_EFFECTS`);
              yield sagaEffects.cancel(task);
            });
          }
        }
      };
    }
    

    通过fork创建了新任务,因为fork本身是无阻塞的,所以当执行了fork的时候,也就执行了${model.namespace}/@@CANCEL_EFFECTS监听,而take是阻塞的,当它被触发时,就调用cancel取消task。在unmodel方法中可以找到:

    store.dispatch({ type: `${namespace}/@@CANCEL_EFFECTS` });
    

    在redux中,创建store的方法如下:

    const store = createStore(
      reducer,
      applyMiddleware(createSagaMiddleware(helloSaga))
    )
    

    第一个参数是reducer,第二个参数是applyMiddleware方法传入各个中间件。现在再来看下dva的创建store过程

    const store = this._store = createStore(
        createReducer(),
        initialState,
        compose(...enhancers),
      );
    

    这里多了initialState,这个是createStore的第二个参数,可选的。第一个参数调用了下面这个方法:

    function createReducer(asyncReducers) {
        return reducerEnhancer(combineReducers({
          ...reducers,
          ...extraReducers,
          ...asyncReducers,
        }));
      }
    

    其中reducerEnhancer是plugin,可有可无的。combineReducers用于合并所有的reducer。这里有意思的还是第三个参数:compose(...enhancers)。 enhancers的定义如下:

    const enhancers = [
        applyMiddleware(...middlewares),
        devtools(),
        ...extraEnhancers,
      ];
    

    因为使用了redux的compose方法,所以第一个元素必须是applyMiddleware

    store.runSaga = sagaMiddleware.run;
    

    这个是redux-saga中的主方法

    store.asyncReducers = {};
    

    注册异步reducers

    // setup history
    if (setupHistory) setupHistory.call(this, history);
    
    // run subscriptions
    const unlisteners = {};
    for (const model of this._models) {
        if (model.subscriptions) {
          unlisteners[model.namespace] = runSubscriptions(model.subscriptions, model, this,
            onErrorWrapper);
        }
    }
    

    这些主要是用来处理subscription的,就不用多说了。

  3. 所以整个下来你会发现:

    1. 主要就是围绕着store的创建,以及在创建store过程中的各种扩展。
    2. dva是很好的封装了redux和saga,使得在一个model中暴露了reducer、effect、subscription。它还提供了各种插件机制方便扩展。
    3. 要想使用好dva,你必须对redux和saga了解的非常全面,任何一个短板都会影响你对dva的使用。特别是当前dva还不支持回调处理等,各种机关需要在实战中摸索出来了。react更不用说了,后面有机会再讨论dva下react的编程模式。

所以说我们发现 dva确实是一个优秀的框架,它不是库。

后面会带来第二篇:DVA源码阅读-插件篇

PS:虽然dva已经极大的方便了开发,但是对于一个一个新建model、route、less、proxy文件还是很累的,而dva-generator就是做这件事的,只要generate-dva bot一行命令就可以为你生成该模型下大部分文件,更多请参见:https://github.com/jnotnull/dva-generator

Most helpful comment

其实就是对react全家桶的封装,最后写个配置就好了。我感觉这种单纯为了简化没有太多数据结构思想的封装不大好,会让新人理解redux、sagas起来更加困难,虽然写起来是简单了,跟以前哪些对backbone,zepto和require的封装一样。个人而言还是喜欢自己来配全家桶,扩展性强点。

>All comments

其实就是对react全家桶的封装,最后写个配置就好了。我感觉这种单纯为了简化没有太多数据结构思想的封装不大好,会让新人理解redux、sagas起来更加困难,虽然写起来是简单了,跟以前哪些对backbone,zepto和require的封装一样。个人而言还是喜欢自己来配全家桶,扩展性强点。

Was this page helpful?
0 / 5 - 0 ratings