Dva 源码解析
https://github.com/dvajs/dva/tree/master/packages

实现 reducers (基本计数器-reducers示例)

dva/index.js

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import {createStore, combineReducers} from 'redux';
  4. import {Provider, connect} from 'react-redux';
  5. import prefixNamespace from './prefixNamespace'
  6. export {
  7. connect,
  8. }
  9. function dva(){
  10. const app = {
  11. model,
  12. _models: [],
  13. router,
  14. _router: null,
  15. start,
  16. }
  17. let initialReducers = {}; //定义一个初始化的reducers合并对象
  18. function model(model){
  19. // 给action添加namespace前缀
  20. // 如:把 add(state, action){} 改为 [counter1/add](state, action){}
  21. const prefixedModel = prefixNamespace(model);
  22. app._models.push(prefixedModel);
  23. return prefixedModel;
  24. }
  25. function router(router){
  26. app._router = router;
  27. }
  28. function start(selector){
  29. // 把每一个model的reducers添加到initialReducers中
  30. app._models.forEach(model => {
  31. initialReducers[model.namespace] = getReducer(model);
  32. })
  33. let rootReducer = createReducer(); //创建根rootReducer
  34. let store = createStore(rootReducer); //创建仓库store
  35. // 合并所有model的reducers
  36. function createReducer(){
  37. return combineReducers(initialReducers);
  38. }
  39. ReactDOM.render((
  40. <Provider store={store}>
  41. {app._router({})}
  42. </Provider>
  43. ), document.querySelector(selector));
  44. }
  45. return app;
  46. };
  47. // 从子model中获取子reducer
  48. function getReducer(model){
  49. const {reducers, state: initialState} = model;
  50. function reducer(state=initialState, action){
  51. const matchReducer = reducers[action.type];
  52. // 如果有匹配,返回新数据;没有匹配到,返回旧数据。
  53. if (matchReducer) return matchReducer(state, action);
  54. return state;
  55. }
  56. return reducer;
  57. }
  58. export default dva;

dva/prefixNamespace.js

  1. function prefix(obj, namespace){
  2. return Object.keys(obj).reduce((memo, key) => {
  3. const newKey = `${namespace}/${key}`;
  4. memo[newKey] = obj[key];
  5. return memo;
  6. }, {})
  7. }
  8. /** 给model的action添加namespace前缀
  9. *
  10. * @param {*} model
  11. */
  12. function prefixNamespace(model){
  13. if (model.reducers){
  14. model.reducers = prefix(model.reducers, model.namespace);
  15. }
  16. return model;
  17. }
  18. export default prefixNamespace;

实现 effects

dva/index.js

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. + import {createStore, combineReducers, applyMiddleware} from 'redux';
  4. import {Provider, connect} from 'react-redux';
  5. + import createSagaMiddleware from 'redux-saga';
  6. import prefixNamespace from './prefixNamespace'
  7. + import getSagas from './getSagas'
  8. export {
  9. connect,
  10. }
  11. function dva(){
  12. const app = {
  13. model,
  14. _models: [],
  15. router,
  16. _router: null,
  17. start,
  18. }
  19. const initialReducers = {}; //定义一个初始化的reducers合并对象
  20. function model(model){
  21. // 给action添加namespace前缀
  22. // 如:把 add(state, action){} 改为 [counter1/add](state, action){}
  23. const prefixedModel = prefixNamespace(model);
  24. app._models.push(prefixedModel);
  25. return prefixedModel;
  26. }
  27. function router(router){
  28. app._router = router;
  29. }
  30. function start(selector){
  31. // 把每一个model的reducers添加到initialReducers中
  32. app._models.forEach(model => {
  33. initialReducers[model.namespace] = getReducer(model);
  34. })
  35. const rootReducer = createReducer(); //创建根rootReducer
  36. + const sagas = getSagas(app); //获取所有model的saga集合 [model1-Saga, model2-Saga]
  37. + const sagaMiddleware = createSagaMiddleware(); //创建saga中间件
  38. + const store = applyMiddleware(sagaMiddleware)(createStore)(rootReducer); // 创建仓库store
  39. + sagas.forEach(saga => sagaMiddleware.run(saga)); //启动saga
  40. // 合并所有model的reducers
  41. function createReducer(){
  42. return combineReducers(initialReducers);
  43. }
  44. ReactDOM.render((
  45. <Provider store={store}>
  46. {app._router({})}
  47. </Provider>
  48. ), document.querySelector(selector));
  49. }
  50. return app;
  51. };
  52. // 从子model中获取子reducer
  53. function getReducer(model){
  54. const {reducers, state: initialState} = model;
  55. function reducer(state=initialState, action){
  56. const matchReducer = reducers[action.type];
  57. // 如果有匹配,返回新数据;没有匹配到,返回旧数据。
  58. if (matchReducer) return matchReducer(state, action);
  59. return state;
  60. }
  61. return reducer;
  62. }
  63. export default dva;

dva/prefixNamespace.js

  1. function prefix(obj, namespace){
  2. return Object.keys(obj).reduce((memo, key) => {
  3. const newKey = `${namespace}/${key}`;
  4. memo[newKey] = obj[key];
  5. return memo;
  6. }, {})
  7. }
  8. /** 给model的action添加namespace前缀
  9. *
  10. * @param {*} model
  11. */
  12. function prefixNamespace(model){
  13. if (model.reducers){
  14. model.reducers = prefix(model.reducers, model.namespace);
  15. }
  16. + if (model.effects){
  17. + model.effects = prefix(model.effects, model.namespace);
  18. + }
  19. return model;
  20. }
  21. export default prefixNamespace;

dva/getSagas.js

  1. import * as sagaEffects from 'redux-saga/effects';
  2. // 返回所有子model saga 的集合
  3. function getSagas(app){
  4. let sagas = [];
  5. for (const model of app._models){
  6. sagas.push(getSaga(model.effects, model));
  7. }
  8. return sagas;
  9. }
  10. /** 返回子model的saga
  11. *
  12. * @param {*} effects
  13. * @param {*} model
  14. */
  15. function getSaga(effects, model){
  16. return function* (){
  17. for (const key in effects){
  18. const watcher = getWatcher(key, effects[key], model);
  19. yield sagaEffects.fork(watcher); //fork一个子进程执行 watcherSaga
  20. }
  21. }
  22. }
  23. /** 获取 watcherSaga (rootSaga -> watcherSaga -> workerSaga)
  24. * 当每次向仓库派发asyncAdd动作的时候,都会执行 asyncAddEffect saga
  25. *
  26. * @param {*} key 监听动作:asyncAdd
  27. * @param {*} effect 工作saga:asyncAddEffect,即:function* asyncAdd(action){...}
  28. * @param {*} model 子model
  29. */
  30. function getWatcher(key, effect, model){
  31. return function* (){
  32. // 每当key动作派发的时候,saga在执行effect(工作saga)的时候,会向它默认传递action
  33. yield sagaEffects.takeEvery(key, function* sagaWithCatch(...args){
  34. yield effect(
  35. ...args,
  36. {
  37. ...sagaEffects,
  38. // 重写put方法,给本model的action.type 加上namespace前缀
  39. put: action => sagaEffects.put({...action, type: prefixType(action.type, model)}),
  40. },
  41. );
  42. });
  43. }
  44. }
  45. /** 给 action.type 加上namespace前缀
  46. *
  47. * @param {*} type 动作类型
  48. * @param {*} model 子model
  49. */
  50. function prefixType(type, model){
  51. const {namespace} = model;
  52. // 如果派发本model的动作,不加前缀
  53. if (type.indexOf('/') === -1){
  54. return `${namespace}/${type}`;
  55. } else {
  56. if (type.split('/')[0] === namespace){
  57. console.warn(`Warning: [sagaEffects.put] ${type} should not be prefixed with namespace ${namespace}`);
  58. }
  59. // 如果派发其它model的动作,需要加前缀
  60. return type;
  61. }
  62. }
  63. export default getSagas;

支持路由

dva/router.js

  1. export * from 'react-router-dom';

dva/index.js

  1. ...
  2. + import {createHashHistory} from 'history';
  3. +function dva(options={}){
  4. ...
  5. + const history = options.history || createHashHistory();
  6. ...
  7. function start(selector){
  8. ...
  9. ReactDOM.render((
  10. <Provider store={store}>
  11. + {app._router({app, history})}
  12. </Provider>
  13. ), document.querySelector(selector));
  14. }
  15. return app;
  16. };
  17. ...
  18. export default dva;

支持路由跳转

dva 源码中用的 react-router-redux,该库现在已废弃,被 connected-react-router 替代了。

dva/router.js

  1. + import * as routerRedux from 'connected-react-router';
  2. export * from 'react-router-dom';
  3. + export {routerRedux};

dva/index.js

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import {createStore, combineReducers, applyMiddleware} from 'redux';
  4. import {Provider, connect} from 'react-redux';
  5. import createSagaMiddleware from 'redux-saga';
  6. + import {routerMiddleware, connectRouter, ConnectedRouter} from 'connected-react-router';
  7. import {createHashHistory} from 'history';
  8. import prefixNamespace from './prefixNamespace'
  9. import getSagas from './getSagas'
  10. export {
  11. connect,
  12. }
  13. function dva(options={}){
  14. const app = {
  15. model,
  16. _models: [],
  17. router,
  18. _router: null,
  19. start,
  20. }
  21. const history = options.history || createHashHistory();
  22. + const initialReducers = {router: connectRouter(history)}; //定义一个初始化的reducers合并对象
  23. function model(model){
  24. // 给action添加namespace前缀
  25. // 如:把 add(state, action){} 改为 [counter1/add](state, action){}
  26. const prefixedModel = prefixNamespace(model);
  27. app._models.push(prefixedModel);
  28. return prefixedModel;
  29. }
  30. function router(router){
  31. app._router = router;
  32. }
  33. function start(selector){
  34. // 把每一个model的reducers添加到initialReducers中
  35. app._models.forEach(model => {
  36. initialReducers[model.namespace] = getReducer(model);
  37. })
  38. const rootReducer = createReducer(); //创建根rootReducer
  39. const sagas = getSagas(app); //获取所有model的saga集合 [model1-Saga, model2-Saga]
  40. const sagaMiddleware = createSagaMiddleware(); //创建saga中间件
  41. // 创建仓库store
  42. const store = applyMiddleware(
  43. + routerMiddleware(history),
  44. sagaMiddleware
  45. )(createStore)(rootReducer);
  46. sagas.forEach(saga => sagaMiddleware.run(saga)); //启动saga
  47. // 合并所有model的reducers
  48. function createReducer(){
  49. return combineReducers(initialReducers);
  50. }
  51. ReactDOM.render((
  52. <Provider store={store}>
  53. {app._router({app, history})}
  54. </Provider>
  55. ), document.querySelector(selector));
  56. }
  57. return app;
  58. };
  59. // 从子model中获取子reducer
  60. function getReducer(model){
  61. const {reducers, state: initialState} = model;
  62. function reducer(state=initialState, action){
  63. const matchReducer = reducers[action.type];
  64. // 如果有匹配,返回新数据;没有匹配到,返回旧数据。
  65. if (matchReducer) return matchReducer(state, action);
  66. return state;
  67. }
  68. return reducer;
  69. }
  70. export default dva;

dva/saga.js

  1. 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

dva-immer

https://github.com/dvajs/dva/tree/master/packages/dva-immer