原文参见: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三个方法。
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;
}
现在重点看下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的,就不用多说了。
所以整个下来你会发现:
所以说我们发现 dva确实是一个优秀的框架,它不是库。
后面会带来第二篇:DVA源码阅读-插件篇
PS:虽然dva已经极大的方便了开发,但是对于一个一个新建model、route、less、proxy文件还是很累的,而dva-generator就是做这件事的,只要generate-dva bot一行命令就可以为你生成该模型下大部分文件,更多请参见:https://github.com/jnotnull/dva-generator
其实就是对react全家桶的封装,最后写个配置就好了。我感觉这种单纯为了简化没有太多数据结构思想的封装不大好,会让新人理解redux、sagas起来更加困难,虽然写起来是简单了,跟以前哪些对backbone,zepto和require的封装一样。个人而言还是喜欢自己来配全家桶,扩展性强点。
Most helpful comment
其实就是对react全家桶的封装,最后写个配置就好了。我感觉这种单纯为了简化没有太多数据结构思想的封装不大好,会让新人理解redux、sagas起来更加困难,虽然写起来是简单了,跟以前哪些对backbone,zepto和require的封装一样。个人而言还是喜欢自己来配全家桶,扩展性强点。