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
dva的三个核心方法
- router,管理路由,封装 react-router-dom
- model,管理数据流,封装 react-redux,redux-saga异步请求
- start,启动项目,封装 ReactDOM.render()
connect连接组件和 Store
组件 dispatch action修改数据,action同时提交到 reducer和 effect里面
- 先执行 reducers里面的方法,后执行 effects里面的
- reducers里面的方法修改只,effects里面获取到的就是最新的值
- effects里面 put(dispatch) 不需要写命名空间前缀
effects从服务端获取数据,然后提交到 reducers里面,reducers修改 store
app.model({namespace: 'counter',state: { number: 10 },reducers: {log(state, action) {return { number: 200 }}},effects: {*log(action, effect) {const state = yield effect.select(state => state.counter)//同名方法修改值;effect里面获取到的就是 reducers里面修改的最新值console.log('effect state', state.number) // 200}}})

dva设计思路
dva有 3个核心方法
- .model()
- .router()
- .start()
同步方法,待完善的
- action.type 没有处理 namespace 前缀,多个 action有命名冲突的可能
- router只能处理一个路由
- dispatch只能处理同步,不能处理异步
dva.js
import React from 'react';import ReactDOM from 'react-dom';import { combineReducers, createStore } from 'redux';import { Provider, connect } from 'react-redux';import { createHashHistory } from 'history';function dva(options={}) {const app = {model, // 添加模型的方法,方法处理器router,start,_models: [], // 定义所有的模型_router: undefined, // 存放路由定定义的函数}// 把 model放到数组里function model(model) {app._models.push(model)// app._models = [{namespace: 'counter'}, {namespace: 'list'}]}// 路由配置function router(routerConfig) {app._router = routerConfig}function start(containerId) {const history = options.history || createHashHistory();const reducers = getReducers(app);const store = createStore(reducers);// application实例,路由传入默认参数const App = app._router({ ...app, history })ReactDOM.render(<Provider store={store}>{ App }</Provider>,document.querySelector(containerId))}return app}export { connect }export default dvafunction getReducers(app) {const rootReducers = {};// app._models = [{namespace: 'counter',state, reducers}, {namespace: 'list'}]for(const model of app._models) {const { namespace, reducers } = model;rootReducers[namespace] = (state = model.state, action) => {const reducer = reducers[action.type]return reducer ? reducer(state, action) : state;}}return combineReducers(rootReducers);}
getReducers
getReducers就是把 model里面的 reducer变成 reducer函数
app.model({namespace: 'counter',state: { number: 0 },// 同步的方法reducers: {add(state, action) {return { number: state.number + (action.payload || 10) }},minus(state) {// state是之前的状态,return返回值是新状态 statereturn { number: state.number - 2 }}}})// 转变成 reducer函数,如何转化?通过 getReducersfunction reducer(state = model.state, action) => {if(action.type === 'counter/add') {return add(state, action)}if(action.type === 'counter/minus') {return minus(state, action);}return state;}
getReducers
function getReducers(app) {const rootReducers = {};// app._models = [{namespace: 'counter',state, reducers}, {namespace: 'list'}]for(const model of app._models) {const { namespace, reducers } = model;rootReducers[namespace] = (state = model.state, action) => {const reducer = reducers[action.type]return reducer ? reducer(state, action) : state;}}return combineReducers(rootReducers);}
index.js
import React from 'react'import { createBrowserHistory } from 'history';import dvaLoading from 'dva-loading'import dva, { connect } from './dva'import './index.less';// 1. Initialize 实例化 dvaconst app = dva({history: createBrowserHistory(),});app.use(dvaLoading())// 每个 model模型都一个自己的命名空间,防止方法重名混乱app.model({namespace: 'counter',state: { number: 0 },// 同步的方法reducers: {// state是之前的状态,return返回值是新状态 stateadd(state, action) {return { number: state.number + (action.payload || 10) }},minus(state) {return { number: state.number - 2 }}}})const Calc = props => {const { number, dispatch } = propsreturn (<><h1>DVA {number}</h1><buttononClick={() => dispatch({ type: 'add' })}>增加 10</button><buttononClick={() => dispatch({ type: 'minus' })}>减少 2</button></>)}// connect连接 state数据和组件const Counter = connect(state => state.counter)(Calc)// 4. Router 声明路由// app.router(require('./router').default)app.router(() => <Counter />) // 一个路由// 5. Start 项目启动,把 app.router的结果渲染到 #root里面app.start('#root')
解决reducers中的namespace
prefixNamespace
此方法就是把 reducers对象的属性从 add 变成 ‘counter/add’
function prefixNamespace(model, delimit = '/') {const { reducers = {}, namespace } = model;const keys = Object.keys(reducers);// 返回新的 reducers 'counter/add'model.reducers = keys.reduce((memo, key) => {const reducerKey = `${namespace}${delimit}${key}`; // counter/addmemo[reducerKey] = reducers[key];return memo;}, {});return model;}
model格式如下,最终把 reducers里面的 add,加上命名空间的前缀,’counter/add’; ‘counter/minus’
// 每个 model模型都一个自己的命名空间,防止方法重名混乱app.model({namespace: 'counter',state: { number: 0 },// 同步的方法reducers: {add(state, action) {return { number: state.number + (action.payload || 10) }},minus(state) {// state是之前的状态,return返回值是新状态 statereturn { number: state.number - 2 }}}})
dva.js,中的model,添加命名空间前缀
function model(model) {app._models.push(model)}// 把 model,修改为function model(model) {const prefixModel = prefixNamespace(model);app._models.push(prefixModel)// app._models = [{namespace: 'counter'}, {namespace: 'list'}]}
dva完整实现
dva/index.js
/*** @description 手写 dva原理,dva没有新的概念,整合了 redux,redux-saga** dva有 3个核心方法* .model()* .router()* .start()** .use() 插件的用法*/import React from 'react'import ReactDOM from 'react-dom'// 合并 reducersimport { combineReducers, createStore, applyMiddleware } from 'redux'import { Provider, connect } from 'react-redux'// createHashHistoryimport { createBrowserHistory } from 'history'// saga中间件,effects副作用import createSagaMiddleware from 'redux-saga'import * as sagaEffects from 'redux-saga/effects'export { connect }const history = createBrowserHistory()function dva() {const app = {model, // 添加模型的方法,方法处理器router,start,_models: [], // 定义所有的模型_router: null, // 存放路由定定义的函数}function model(model) {app._models.push(model)}function router(routerConfig) {// 路由配置app._router = routerConfig}function start(containerId) {// application实例,路由传入默认参数const App = app._router({ ...app, history })// 多个 app.model() 需要合并const reducers = {} // app.models.reduce()const { length } = app._modelsfor(let i=0; i < length; i++) {const model = app._models[i]// 每个model合并为一个 reducer,key是namespace的值,value是一个reducer函数reducers[model.namespace] = function(state = model.state, action={}) {// 获取 action动作类型 'counter/add'const [namespace, type] = action.type.split('/')// 当 action派发的动作的命名空间,和当前方法(reducer)的命名空间相同的时候if (model.namespace === namespace) {const reducer = model.reducers[type]if (reducer) {return reducer(state, action)}}return state}}/*每一个 model模型都有namespace,都是状态树中的子属性,都有一个子的reducersapp.model({namespace: 'counter',state: { number: 0 }, 初始值reducers: {add(state, action) { // key: add, value: 函数return { number: state.number + 10 }}}}) */// combineReducers合并的时候传入一个对象,key是合并后的属性名,value是处理函数const rootReducer = combineReducers(reducers)// 只有 reducers同步方法// const store = createStore(rootReducer)// 返回一个 saga中间件,处理异步方法const sagaMiddleware = createSagaMiddleware()const store = createStore(rootReducer, applyMiddleware(sagaMiddleware))// 运行异步方法,rootSata执行 effect里面的异步方法sagaMiddleware.run(rootSaga)// eslint-disable-next-line require-yieldfunction* rootSaga() {for (const model of app._models) {const { effects = {}, namespace } = model// key = asyncAddfor(const key in effects) {const attr = `${namespace}/${key}`// takeEvery监听每一个动作,当动作发生的时候,执行对应的 saga// eslint-disable-next-line no-loop-funcsagaEffects.takeEvery(attr, function* (action){yield effects[key](action, sagaEffects)})}}}// getElementById 报错???静态获取ReactDOM.render(<Provider store={store}>{ App }</Provider>,document.querySelector(containerId))}return app}export default dva
dav/router
dva/router.js
// export * from 'react-router-dom'// module.exports = require('react-router-dom')import { Router, Route, Link } from 'react-router-dom'export {Router, Route, Link}
