dva源码分析
1.简介
dva首先是一个基于
redux和redux-saga的数据流方案,为了简化开发体验,dva 还额外内置了react-router和fetch,所以也可以理解为一个轻量级的应用框架
2.使用dva
- history的最新版本为5.0,而
connect-react-router使用的history版本为4.7,不兼容 - 不显示安装history或者指定老版本history进行安装。否则自己安装history是使用history5
2.1.项目依赖
npm install dva redux react-redux redux-saga react-router-dom connected-reac-router -S
2.2.src/index.js
import React from 'react';
import dva, { connect } from 'dva';
//dva是一个函数,执行它可以得到app实例
const app = dva();
//调用model方法定义一个模型
app.model({
namespace: 'counter',//命名空间
state: { num: 0 },//此命名空间下的初始状态
reducers: {//处理器
add(state) {//处理器对象的key,其实就是action.type,此函数可以接收老状态,返回新状态
return { num: state.num + 1 }
}
}
})
function Counter(props) {
return <div>
<p>{props.num}</p>
<button onClick={()=>props.dispatch({ type: 'counter/add' })}>+</button>
</div>
}
const ConnectedCounter = connect(state => state.counter)(Counter);
//定义路由
app.router(() => <ConnectedCounter />);
//开始渲染,把app.router方法的返回值渲染到root容器
app.start('#root');
3.基本功能实现
3.1.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: [],
model,
_router: null,
router,
start,
};
function model(model) {
//经过转化后的model,将namespace拼接到reducers方法上
const prefixedModel = prefixNamespace(model);
app._model.push(prefixedModel);
}
function router(router) {//参数是一个函数
app._router = router
}
const initialReducers = {};
function start(root) {
for (const model of app._model) {
initialReducers[model.namespace] = getReducer(model);
}
const rootReducer = createReducer()
const store = createStore(rootReducer)
ReactDOM.render(<Provider store={store}>{
app._router()
}</Provider>, document.querySelector(root));
function createReducer() {
return combineReducers(initialReducers)
}
}
return app;
}
function getReducer(model) {
const { state: initialState, reducers } = model;
const reducer = (state = initialState, action) => {
const reducer = reducers[action.type];//action.type=counter/add
if (reducer) return reducer(state);
return state;
}
return reducer;
}
export default dva;
3.2.dva/prefixNamespace.js
function prefixNamespace(model) {
const { namespace, reducers } = model;
model.reducers = prefix(namespace, reducers);
return model;
}
function prefix(namespace, obj) {
return Object.keys(obj).reduce((memo, key) => {
const newKey = `${namespace}/${key}`;
memo[newKey] = obj[key];
return memo;
}, {})
}
export default prefixNamespace;
4.支持effects
4.1.基本使用
import React from 'react';
import dva, { connect } from './dva';
//dva是一个函数,执行它可以得到app实例
const app = dva();
//调用model方法定义一个模型
app.model({
namespace: 'counter',//命名空间
state: { num: 0 },//此命名空间下的初始状态
reducers: {//处理器
add(state) {//处理器对象的key,其实就是action.type,此函数可以接收老状态,返回新状态
return { num: state.num + 1 }
}
},
+ effects: {
/**
*
* @param {*} action 动作
* @param {*} effects redux-saga/effects={put ,take,call,all,takeEvery,...}
*/
+ *asyncAdd(action, effects) {
+ const { call, put } = effects;
+ yield call(delay, 1000);
+ yield put({ type: 'add' })
+ }
+ }
})
function Counter(props) {
return <div>
<p>{props.num}</p>
<button onClick={() => props.dispatch({ type: 'counter/add' })}>+</button>
+ <button onClick={() => props.dispatch({ type: 'counter/asyncAdd' })}>asyncAdd</button>
</div>
}
const ConnectedCounter = connect(state => state.counter)(Counter);
//定义路由
app.router(() => <ConnectedCounter />);
//开始渲染,把app.router方法的返回值渲染到root容器
app.start('#root');
+ function delay(ms) {
+ return new Promise(resolve => {
+ setTimeout(resolve, ms);
+ })
}
4.2.实现
4.2.1.dva/prefixNamespace.js
function prefixNamespace(model) {
const { namespace, reducers, effects } = model;
if (reducers) {
model.reducers = prefix(namespace, reducers);
}
+ if (effects) {
+ model.effects = prefix(namespace, effects);
+ }
return model;
}
function prefix(namespace, obj) {
return Object.keys(obj).reduce((memo, key) => {
const newKey = `${namespace}/${key}`;
memo[newKey] = obj[key];
return memo;
}, {})
}
export default prefixNamespace;
4.2.2.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 prefixNamespace from "./prefixNamespace";
+ import createSagaMiddleware from 'redux-saga';
+ import * as sagaEffects from 'redux-saga/effects';
export { connect }
function dva() {
const app = {
_model: [],
model,
_router: null,
router,
start,
};
function model(model) {
//经过转化后的model,将namespace拼接到reducers方法上
const prefixedModel = prefixNamespace(model);
app._model.push(prefixedModel);
}
function router(router) {//参数是一个函数
app._router = router
}
const initialReducers = {};
function start(root) {
for (const model of app._model) {
initialReducers[model.namespace] = getReducer(model);
}
const rootReducer = createReducer()
+ const sagas = getSagas(app)
+ const sagaMiddleware = createSagaMiddleware();
+ const store = applyMiddleware(sagaMiddleware)(createStore)(rootReducer);
+ sagas.forEach(sagaMiddleware.run)
ReactDOM.render(<Provider store={store}>{
app._router()
}</Provider>, document.querySelector(root));
function createReducer() {
return combineReducers(initialReducers)
}
}
+ function getSagas(app) {
+ const sagas = [];
+ for (const model of app._model) {
+ sagas.push(getSaga(model.effects, model))
+ }
+ return sagas;
+ }
return app;
}
function getReducer(model) {
const { state: initialState, reducers } = model;
const reducer = (state = initialState, action) => {
const reducer = reducers[action.type];//action.type=counter/add
if (reducer) return reducer(state);
return state;
}
return reducer;
}
+ function getSaga(effects, model) {
+ return function* () {
+ for (const key in effects) {
+ const watcher = getWatcher(key, effects[key], model);
+ yield sagaEffects.fork(watcher);
+ }
+ }
+ }
+ function getWatcher(key, effect, model) {
+ return function* () {
+ yield sagaEffects.takeEvery(key, function* (action) {
+ yield effect(action, {
+ ...sagaEffects,
+ put: action => sagaEffects.put({
+ ...action,
+ type: prefixType(action.type, model.namespace)
+ })
+ })
+ })
+ }
+ }
+ function prefixType(actionType, namespace) {
+ if (actionType.includes('/'))
+ return actionType
+ return `${namespace}/${actionType}`
+ }
export default dva;