DVA源码阅读-初始化篇
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三个方法。
-
model和router也很简单,就是分别在
_models数组中注入model以及初始化_routerfunction model(model) { this._models.push(checkModel(model, mobile)); } function router(router) { invariant(typeof router === 'function', 'app.router: router should be function'); this._router = router; } -
现在重点看下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的,就不用多说了。
-
所以整个下来你会发现:
- 主要就是围绕着store的创建,以及在创建store过程中的各种扩展。
- dva是很好的封装了redux和saga,使得在一个model中暴露了reducer、effect、subscription。它还提供了各种插件机制方便扩展。
- 要想使用好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 等忙完这一阵子的吧 最近事情有点多