dva-generator icon indicating copy to clipboard operation
dva-generator copied to clipboard

DVA源码阅读-初始化篇

Open jnotnull opened this issue 8 years ago • 2 comments

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

jnotnull avatar Apr 02 '17 06:04 jnotnull

第二篇什么时候出?期待

daskyrk avatar May 16 '17 08:05 daskyrk

@daskyrk 等忙完这一阵子的吧 最近事情有点多

jnotnull avatar May 17 '17 01:05 jnotnull