Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DVA源码阅读-初始化篇 #1

Open
jnotnull opened this issue Apr 2, 2017 · 2 comments
Open

DVA源码阅读-初始化篇 #1

jnotnull opened this issue Apr 2, 2017 · 2 comments

Comments

@jnotnull
Copy link
Owner

jnotnull commented Apr 2, 2017

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

@daskyrk
Copy link

daskyrk commented May 16, 2017

第二篇什么时候出?期待

@jnotnull
Copy link
Owner Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants