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 dva
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);
}
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返回值是新状态 state
return { number: state.number - 2 }
}
}
})
// 转变成 reducer函数,如何转化?通过 getReducers
function 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 实例化 dva
const app = dva({
history: createBrowserHistory(),
});
app.use(dvaLoading())
// 每个 model模型都一个自己的命名空间,防止方法重名混乱
app.model({
namespace: 'counter',
state: { number: 0 },
// 同步的方法
reducers: {
// state是之前的状态,return返回值是新状态 state
add(state, action) {
return { number: state.number + (action.payload || 10) }
},
minus(state) {
return { number: state.number - 2 }
}
}
})
const Calc = props => {
const { number, dispatch } = props
return (
<>
<h1>DVA {number}</h1>
<button
onClick={() => dispatch({ type: 'add' })}
>增加 10</button>
<button
onClick={() => 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/add
memo[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返回值是新状态 state
return { 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'
// 合并 reducers
import { combineReducers, createStore, applyMiddleware } from 'redux'
import { Provider, connect } from 'react-redux'
// createHashHistory
import { 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._models
for(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,都是状态树中的子属性,都有一个子的reducers
app.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-yield
function* rootSaga() {
for (const model of app._models) {
const { effects = {}, namespace } = model
// key = asyncAdd
for(const key in effects) {
const attr = `${namespace}/${key}`
// takeEvery监听每一个动作,当动作发生的时候,执行对应的 saga
// eslint-disable-next-line no-loop-func
sagaEffects.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
}