Dva 源码解析
https://github.com/dvajs/dva/tree/master/packages
实现 reducers (基本计数器-reducers示例)
dva/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, combineReducers} from 'redux';
import {Provider, connect} from 'react-redux';
import prefixNamespace from './prefixNamespace'
export {
connect,
}
function dva(){
const app = {
model,
_models: [],
router,
_router: null,
start,
}
let initialReducers = {}; //定义一个初始化的reducers合并对象
function model(model){
// 给action添加namespace前缀
// 如:把 add(state, action){} 改为 [counter1/add](state, action){}
const prefixedModel = prefixNamespace(model);
app._models.push(prefixedModel);
return prefixedModel;
}
function router(router){
app._router = router;
}
function start(selector){
// 把每一个model的reducers添加到initialReducers中
app._models.forEach(model => {
initialReducers[model.namespace] = getReducer(model);
})
let rootReducer = createReducer(); //创建根rootReducer
let store = createStore(rootReducer); //创建仓库store
// 合并所有model的reducers
function createReducer(){
return combineReducers(initialReducers);
}
ReactDOM.render((
<Provider store={store}>
{app._router({})}
</Provider>
), document.querySelector(selector));
}
return app;
};
// 从子model中获取子reducer
function getReducer(model){
const {reducers, state: initialState} = model;
function reducer(state=initialState, action){
const matchReducer = reducers[action.type];
// 如果有匹配,返回新数据;没有匹配到,返回旧数据。
if (matchReducer) return matchReducer(state, action);
return state;
}
return reducer;
}
export default dva;
dva/prefixNamespace.js
function prefix(obj, namespace){
return Object.keys(obj).reduce((memo, key) => {
const newKey = `${namespace}/${key}`;
memo[newKey] = obj[key];
return memo;
}, {})
}
/** 给model的action添加namespace前缀
*
* @param {*} model
*/
function prefixNamespace(model){
if (model.reducers){
model.reducers = prefix(model.reducers, model.namespace);
}
return model;
}
export default prefixNamespace;
实现 effects
dva/index.js
import React from 'react';
import ReactDOM from 'react-dom';
+ import {createStore, combineReducers, applyMiddleware} from 'redux';
import {Provider, connect} from 'react-redux';
+ import createSagaMiddleware from 'redux-saga';
import prefixNamespace from './prefixNamespace'
+ import getSagas from './getSagas'
export {
connect,
}
function dva(){
const app = {
model,
_models: [],
router,
_router: null,
start,
}
const initialReducers = {}; //定义一个初始化的reducers合并对象
function model(model){
// 给action添加namespace前缀
// 如:把 add(state, action){} 改为 [counter1/add](state, action){}
const prefixedModel = prefixNamespace(model);
app._models.push(prefixedModel);
return prefixedModel;
}
function router(router){
app._router = router;
}
function start(selector){
// 把每一个model的reducers添加到initialReducers中
app._models.forEach(model => {
initialReducers[model.namespace] = getReducer(model);
})
const rootReducer = createReducer(); //创建根rootReducer
+ const sagas = getSagas(app); //获取所有model的saga集合 [model1-Saga, model2-Saga]
+ const sagaMiddleware = createSagaMiddleware(); //创建saga中间件
+ const store = applyMiddleware(sagaMiddleware)(createStore)(rootReducer); // 创建仓库store
+ sagas.forEach(saga => sagaMiddleware.run(saga)); //启动saga
// 合并所有model的reducers
function createReducer(){
return combineReducers(initialReducers);
}
ReactDOM.render((
<Provider store={store}>
{app._router({})}
</Provider>
), document.querySelector(selector));
}
return app;
};
// 从子model中获取子reducer
function getReducer(model){
const {reducers, state: initialState} = model;
function reducer(state=initialState, action){
const matchReducer = reducers[action.type];
// 如果有匹配,返回新数据;没有匹配到,返回旧数据。
if (matchReducer) return matchReducer(state, action);
return state;
}
return reducer;
}
export default dva;
dva/prefixNamespace.js
function prefix(obj, namespace){
return Object.keys(obj).reduce((memo, key) => {
const newKey = `${namespace}/${key}`;
memo[newKey] = obj[key];
return memo;
}, {})
}
/** 给model的action添加namespace前缀
*
* @param {*} model
*/
function prefixNamespace(model){
if (model.reducers){
model.reducers = prefix(model.reducers, model.namespace);
}
+ if (model.effects){
+ model.effects = prefix(model.effects, model.namespace);
+ }
return model;
}
export default prefixNamespace;
dva/getSagas.js
import * as sagaEffects from 'redux-saga/effects';
// 返回所有子model saga 的集合
function getSagas(app){
let sagas = [];
for (const model of app._models){
sagas.push(getSaga(model.effects, model));
}
return sagas;
}
/** 返回子model的saga
*
* @param {*} effects
* @param {*} model
*/
function getSaga(effects, model){
return function* (){
for (const key in effects){
const watcher = getWatcher(key, effects[key], model);
yield sagaEffects.fork(watcher); //fork一个子进程执行 watcherSaga
}
}
}
/** 获取 watcherSaga (rootSaga -> watcherSaga -> workerSaga)
* 当每次向仓库派发asyncAdd动作的时候,都会执行 asyncAddEffect saga
*
* @param {*} key 监听动作:asyncAdd
* @param {*} effect 工作saga:asyncAddEffect,即:function* asyncAdd(action){...}
* @param {*} model 子model
*/
function getWatcher(key, effect, model){
return function* (){
// 每当key动作派发的时候,saga在执行effect(工作saga)的时候,会向它默认传递action
yield sagaEffects.takeEvery(key, function* sagaWithCatch(...args){
yield effect(
...args,
{
...sagaEffects,
// 重写put方法,给本model的action.type 加上namespace前缀
put: action => sagaEffects.put({...action, type: prefixType(action.type, model)}),
},
);
});
}
}
/** 给 action.type 加上namespace前缀
*
* @param {*} type 动作类型
* @param {*} model 子model
*/
function prefixType(type, model){
const {namespace} = model;
// 如果派发本model的动作,不加前缀
if (type.indexOf('/') === -1){
return `${namespace}/${type}`;
} else {
if (type.split('/')[0] === namespace){
console.warn(`Warning: [sagaEffects.put] ${type} should not be prefixed with namespace ${namespace}`);
}
// 如果派发其它model的动作,需要加前缀
return type;
}
}
export default getSagas;
支持路由
dva/router.js
export * from 'react-router-dom';
dva/index.js
...
+ import {createHashHistory} from 'history';
+function dva(options={}){
...
+ const history = options.history || createHashHistory();
...
function start(selector){
...
ReactDOM.render((
<Provider store={store}>
+ {app._router({app, history})}
</Provider>
), document.querySelector(selector));
}
return app;
};
...
export default dva;
支持路由跳转
dva 源码中用的 react-router-redux,该库现在已废弃,被 connected-react-router 替代了。
dva/router.js
+ import * as routerRedux from 'connected-react-router';
export * from 'react-router-dom';
+ export {routerRedux};
dva/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, combineReducers, applyMiddleware} from 'redux';
import {Provider, connect} from 'react-redux';
import createSagaMiddleware from 'redux-saga';
+ import {routerMiddleware, connectRouter, ConnectedRouter} from 'connected-react-router';
import {createHashHistory} from 'history';
import prefixNamespace from './prefixNamespace'
import getSagas from './getSagas'
export {
connect,
}
function dva(options={}){
const app = {
model,
_models: [],
router,
_router: null,
start,
}
const history = options.history || createHashHistory();
+ const initialReducers = {router: connectRouter(history)}; //定义一个初始化的reducers合并对象
function model(model){
// 给action添加namespace前缀
// 如:把 add(state, action){} 改为 [counter1/add](state, action){}
const prefixedModel = prefixNamespace(model);
app._models.push(prefixedModel);
return prefixedModel;
}
function router(router){
app._router = router;
}
function start(selector){
// 把每一个model的reducers添加到initialReducers中
app._models.forEach(model => {
initialReducers[model.namespace] = getReducer(model);
})
const rootReducer = createReducer(); //创建根rootReducer
const sagas = getSagas(app); //获取所有model的saga集合 [model1-Saga, model2-Saga]
const sagaMiddleware = createSagaMiddleware(); //创建saga中间件
// 创建仓库store
const store = applyMiddleware(
+ routerMiddleware(history),
sagaMiddleware
)(createStore)(rootReducer);
sagas.forEach(saga => sagaMiddleware.run(saga)); //启动saga
// 合并所有model的reducers
function createReducer(){
return combineReducers(initialReducers);
}
ReactDOM.render((
<Provider store={store}>
{app._router({app, history})}
</Provider>
), document.querySelector(selector));
}
return app;
};
// 从子model中获取子reducer
function getReducer(model){
const {reducers, state: initialState} = model;
function reducer(state=initialState, action){
const matchReducer = reducers[action.type];
// 如果有匹配,返回新数据;没有匹配到,返回旧数据。
if (matchReducer) return matchReducer(state, action);
return state;
}
return reducer;
}
export default dva;
dva/saga.js
export * from 'redux-saga';
dva/dynamic
https://github.com/dvajs/dva/blob/master/packages/dva/src/dynamic.js
dva-loading
https://github.com/dvajs/dva/tree/master/packages/dva-loading