dva源码参考 https://github.com/dvajs/dva/tree/master/packages/dva-core/src

dva分析文档
https://www.yuque.com/flying.ni/the-tower/tvzasn
https://blog.csdn.net/yehuozhili/category_9673656.html

Desktop.zip

dva的三个核心方法

  1. router,管理路由,封装 react-router-dom
  2. model,管理数据流,封装 react-redux,redux-saga异步请求
  3. start,启动项目,封装 ReactDOM.render()

connect连接组件和 Store
组件 dispatch action修改数据,action同时提交到 reducer和 effect里面

  1. 先执行 reducers里面的方法,后执行 effects里面的
    1. reducers里面的方法修改只,effects里面获取到的就是最新的值
  2. effects里面 put(dispatch) 不需要写命名空间前缀

effects从服务端获取数据,然后提交到 reducers里面,reducers修改 store

  1. app.model({
  2. namespace: 'counter',
  3. state: { number: 10 },
  4. reducers: {
  5. log(state, action) {
  6. return { number: 200 }
  7. }
  8. },
  9. effects: {
  10. *log(action, effect) {
  11. const state = yield effect.select(state => state.counter)
  12. //同名方法修改值;effect里面获取到的就是 reducers里面修改的最新值
  13. console.log('effect state', state.number) // 200
  14. }
  15. }
  16. })

image.png

dva设计思路

dva有 3个核心方法

  • .model()
  • .router()
  • .start()

同步方法,待完善的

  • action.type 没有处理 namespace 前缀,多个 action有命名冲突的可能
  • router只能处理一个路由
  • dispatch只能处理同步,不能处理异步

dva.js

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { combineReducers, createStore } from 'redux';
  4. import { Provider, connect } from 'react-redux';
  5. import { createHashHistory } from 'history';
  6. function dva(options={}) {
  7. const app = {
  8. model, // 添加模型的方法,方法处理器
  9. router,
  10. start,
  11. _models: [], // 定义所有的模型
  12. _router: undefined, // 存放路由定定义的函数
  13. }
  14. // 把 model放到数组里
  15. function model(model) {
  16. app._models.push(model)
  17. // app._models = [{namespace: 'counter'}, {namespace: 'list'}]
  18. }
  19. // 路由配置
  20. function router(routerConfig) {
  21. app._router = routerConfig
  22. }
  23. function start(containerId) {
  24. const history = options.history || createHashHistory();
  25. const reducers = getReducers(app);
  26. const store = createStore(reducers);
  27. // application实例,路由传入默认参数
  28. const App = app._router({ ...app, history })
  29. ReactDOM.render(
  30. <Provider store={store}>{ App }</Provider>,
  31. document.querySelector(containerId)
  32. )
  33. }
  34. return app
  35. }
  36. export { connect }
  37. export default dva
  38. function getReducers(app) {
  39. const rootReducers = {};
  40. // app._models = [{namespace: 'counter',state, reducers}, {namespace: 'list'}]
  41. for(const model of app._models) {
  42. const { namespace, reducers } = model;
  43. rootReducers[namespace] = (state = model.state, action) => {
  44. const reducer = reducers[action.type]
  45. return reducer ? reducer(state, action) : state;
  46. }
  47. }
  48. return combineReducers(rootReducers);
  49. }

getReducers

getReducers就是把 model里面的 reducer变成 reducer函数

  1. app.model({
  2. namespace: 'counter',
  3. state: { number: 0 },
  4. // 同步的方法
  5. reducers: {
  6. add(state, action) {
  7. return { number: state.number + (action.payload || 10) }
  8. },
  9. minus(state) {
  10. // state是之前的状态,return返回值是新状态 state
  11. return { number: state.number - 2 }
  12. }
  13. }
  14. })
  15. // 转变成 reducer函数,如何转化?通过 getReducers
  16. function reducer(state = model.state, action) => {
  17. if(action.type === 'counter/add') {
  18. return add(state, action)
  19. }
  20. if(action.type === 'counter/minus') {
  21. return minus(state, action);
  22. }
  23. return state;
  24. }

getReducers

  1. function getReducers(app) {
  2. const rootReducers = {};
  3. // app._models = [{namespace: 'counter',state, reducers}, {namespace: 'list'}]
  4. for(const model of app._models) {
  5. const { namespace, reducers } = model;
  6. rootReducers[namespace] = (state = model.state, action) => {
  7. const reducer = reducers[action.type]
  8. return reducer ? reducer(state, action) : state;
  9. }
  10. }
  11. return combineReducers(rootReducers);
  12. }

index.js

  1. import React from 'react'
  2. import { createBrowserHistory } from 'history';
  3. import dvaLoading from 'dva-loading'
  4. import dva, { connect } from './dva'
  5. import './index.less';
  6. // 1. Initialize 实例化 dva
  7. const app = dva({
  8. history: createBrowserHistory(),
  9. });
  10. app.use(dvaLoading())
  11. // 每个 model模型都一个自己的命名空间,防止方法重名混乱
  12. app.model({
  13. namespace: 'counter',
  14. state: { number: 0 },
  15. // 同步的方法
  16. reducers: {
  17. // state是之前的状态,return返回值是新状态 state
  18. add(state, action) {
  19. return { number: state.number + (action.payload || 10) }
  20. },
  21. minus(state) {
  22. return { number: state.number - 2 }
  23. }
  24. }
  25. })
  26. const Calc = props => {
  27. const { number, dispatch } = props
  28. return (
  29. <>
  30. <h1>DVA {number}</h1>
  31. <button
  32. onClick={() => dispatch({ type: 'add' })}
  33. >增加 10</button>
  34. <button
  35. onClick={() => dispatch({ type: 'minus' })}
  36. >减少 2</button>
  37. </>
  38. )
  39. }
  40. // connect连接 state数据和组件
  41. const Counter = connect(state => state.counter)(Calc)
  42. // 4. Router 声明路由
  43. // app.router(require('./router').default)
  44. app.router(() => <Counter />) // 一个路由
  45. // 5. Start 项目启动,把 app.router的结果渲染到 #root里面
  46. app.start('#root')

解决reducers中的namespace

prefixNamespace
此方法就是把 reducers对象的属性从 add 变成 ‘counter/add’

  1. function prefixNamespace(model, delimit = '/') {
  2. const { reducers = {}, namespace } = model;
  3. const keys = Object.keys(reducers);
  4. // 返回新的 reducers 'counter/add'
  5. model.reducers = keys.reduce((memo, key) => {
  6. const reducerKey = `${namespace}${delimit}${key}`; // counter/add
  7. memo[reducerKey] = reducers[key];
  8. return memo;
  9. }, {});
  10. return model;
  11. }

model格式如下,最终把 reducers里面的 add,加上命名空间的前缀,’counter/add’; ‘counter/minus’

  1. // 每个 model模型都一个自己的命名空间,防止方法重名混乱
  2. app.model({
  3. namespace: 'counter',
  4. state: { number: 0 },
  5. // 同步的方法
  6. reducers: {
  7. add(state, action) {
  8. return { number: state.number + (action.payload || 10) }
  9. },
  10. minus(state) {
  11. // state是之前的状态,return返回值是新状态 state
  12. return { number: state.number - 2 }
  13. }
  14. }
  15. })

dva.js,中的model,添加命名空间前缀

  1. function model(model) {
  2. app._models.push(model)
  3. }
  4. // 把 model,修改为
  5. function model(model) {
  6. const prefixModel = prefixNamespace(model);
  7. app._models.push(prefixModel)
  8. // app._models = [{namespace: 'counter'}, {namespace: 'list'}]
  9. }

dva完整实现

dva/index.js

  1. /**
  2. * @description 手写 dva原理,dva没有新的概念,整合了 redux,redux-saga
  3. *
  4. * dva有 3个核心方法
  5. * .model()
  6. * .router()
  7. * .start()
  8. *
  9. * .use() 插件的用法
  10. */
  11. import React from 'react'
  12. import ReactDOM from 'react-dom'
  13. // 合并 reducers
  14. import { combineReducers, createStore, applyMiddleware } from 'redux'
  15. import { Provider, connect } from 'react-redux'
  16. // createHashHistory
  17. import { createBrowserHistory } from 'history'
  18. // saga中间件,effects副作用
  19. import createSagaMiddleware from 'redux-saga'
  20. import * as sagaEffects from 'redux-saga/effects'
  21. export { connect }
  22. const history = createBrowserHistory()
  23. function dva() {
  24. const app = {
  25. model, // 添加模型的方法,方法处理器
  26. router,
  27. start,
  28. _models: [], // 定义所有的模型
  29. _router: null, // 存放路由定定义的函数
  30. }
  31. function model(model) {
  32. app._models.push(model)
  33. }
  34. function router(routerConfig) {
  35. // 路由配置
  36. app._router = routerConfig
  37. }
  38. function start(containerId) {
  39. // application实例,路由传入默认参数
  40. const App = app._router({ ...app, history })
  41. // 多个 app.model() 需要合并
  42. const reducers = {} // app.models.reduce()
  43. const { length } = app._models
  44. for(let i=0; i < length; i++) {
  45. const model = app._models[i]
  46. // 每个model合并为一个 reducer,key是namespace的值,value是一个reducer函数
  47. reducers[model.namespace] = function(state = model.state, action={}) {
  48. // 获取 action动作类型 'counter/add'
  49. const [namespace, type] = action.type.split('/')
  50. // 当 action派发的动作的命名空间,和当前方法(reducer)的命名空间相同的时候
  51. if (model.namespace === namespace) {
  52. const reducer = model.reducers[type]
  53. if (reducer) {
  54. return reducer(state, action)
  55. }
  56. }
  57. return state
  58. }
  59. }
  60. /*
  61. 每一个 model模型都有namespace,都是状态树中的子属性,都有一个子的reducers
  62. app.model({
  63. namespace: 'counter',
  64. state: { number: 0 }, 初始值
  65. reducers: {
  66. add(state, action) { // key: add, value: 函数
  67. return { number: state.number + 10 }
  68. }
  69. }
  70. }) */
  71. // combineReducers合并的时候传入一个对象,key是合并后的属性名,value是处理函数
  72. const rootReducer = combineReducers(reducers)
  73. // 只有 reducers同步方法
  74. // const store = createStore(rootReducer)
  75. // 返回一个 saga中间件,处理异步方法
  76. const sagaMiddleware = createSagaMiddleware()
  77. const store = createStore(rootReducer, applyMiddleware(sagaMiddleware))
  78. // 运行异步方法,rootSata执行 effect里面的异步方法
  79. sagaMiddleware.run(rootSaga)
  80. // eslint-disable-next-line require-yield
  81. function* rootSaga() {
  82. for (const model of app._models) {
  83. const { effects = {}, namespace } = model
  84. // key = asyncAdd
  85. for(const key in effects) {
  86. const attr = `${namespace}/${key}`
  87. // takeEvery监听每一个动作,当动作发生的时候,执行对应的 saga
  88. // eslint-disable-next-line no-loop-func
  89. sagaEffects.takeEvery(attr, function* (action){
  90. yield effects[key](action, sagaEffects)
  91. })
  92. }
  93. }
  94. }
  95. // getElementById 报错???静态获取
  96. ReactDOM.render(
  97. <Provider store={store}>{ App }</Provider>,
  98. document.querySelector(containerId)
  99. )
  100. }
  101. return app
  102. }
  103. export default dva

dav/router

dva/router.js

  1. // export * from 'react-router-dom'
  2. // module.exports = require('react-router-dom')
  3. import { Router, Route, Link } from 'react-router-dom'
  4. export {
  5. Router, Route, Link
  6. }