redux
、redux-saga
和 react-router
的轻量级前端框架。(Inspired by elm and choo)dispatch({
type: 'user/add', // 如果在 model 外调用,需要添加 namespace
payload: {}, // 需要传递的信息
});
create-react-app dva-app
cd dva-app
cnpm i dva keymaster -S
官方推荐的:
├── /mock/ # 数据mock的接口文件
├── /src/ # 项目源码目录
│ ├── /components/ # 项目组件
│ ├── /routes/ # 路由组件(页面维度)
│ ├── /models/ # 数据模型
│ ├── /services/ # 数据接口
│ ├── /utils/ # 工具函数
│ ├── route.js # 路由配置
│ ├── index.js # 入口文件
│ ├── index.less
│ └── index.html
├── package.json # 定义依赖的pkg文件
└── proxy.config.js # 数据mock配置文件
用法 | 说明 |
---|---|
app = dva(opts) | 创建应用,返回 dva 实例 |
app.use(hooks) | 配置 hooks 或者注册插件 |
app.model(model) | 注册 model |
app.router(({ history, app }) => RouterConfig) | 注册路由表 |
app.start(selector?) | 启动应用。selector 可选 |
import React from 'react';
import { Dispatch } from 'redux';
import dva, { connect } from 'dva';
import keymaster from 'keymaster';
import { RouterAPI } from 'dva';
import { Router, Route } from 'dva/router';
interface Counter1State {
number: 0
}
interface Counter2State {
number: 0
}
interface CombinedState {
counter1: Counter1State;
counter2: Counter2State;
}
const app = dva();
const delay = (millseconds: number) => {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve();
}, millseconds);
});
}
app.model({
namespace: 'counter1',
state: { number: 0 },
reducers: {//接收老状态,返回新状态
add(state) { //dispatch({type:'add'});
return { number: state.number + 1 };
},
minus(state) {//dispatch({type:'minus'})
return { number: state.number - 1 };
}
},
// 延时操作 调用接口 等待
effects: {
*asyncAdd(action, { put, call }) { //redux-saga/effects {put,call}
yield call(delay, 1000);//把100传给delay并调用,yield会等待promise完成
yield put({ type: 'add' });
}
},
subscriptions: {
keyboard({ dispatch }) {
keymaster('space', () => {
dispatch({ type: 'add' });
});
},
changeTitle({ history }) {
setTimeout(function () {
history.listen(({ pathname }) => {
document.title = pathname;
});
}, 1000);
}
}
});
app.model({
namespace: 'counter2',
state: { number: 0 },
reducers: {//接收老状态,返回新状态
add(state) { //dispatch({type:'add'});
return { number: state.number + 1 };
},
minus(state) {//dispatch({type:'minus'})
return { number: state.number - 1 };
}
}
});
type Counter1Props = Counter1State & { dispatch: Dispatch };
const Counter1 = (props: Counter1Props) => {
return (
<div>
<p>{props.number}</p>
<button onClick={() => props.dispatch({ type: 'counter1/add' })}>add</button>
<button onClick={() => props.dispatch({ type: 'counter1/asyncAdd' })}>asyncAdd</button>
<button onClick={() => props.dispatch({ type: 'counter1/minus' })}>-</button>
</div>
)
}
type Counter2Props = Counter2State & { dispatch: Dispatch };
const Counter2 = (props: Counter2Props) => {
return (
<div>
<p>{props.number}</p>
<button onClick={() => props.dispatch({ type: 'counter2/add' })}>+</button>
<button onClick={() => props.dispatch({ type: 'counter2/minus' })}>-</button>
</div>
)
}
const mapStateToProps1 = (state: CombinedState): Counter1State => state.counter1;
const ConnectedCounter = connect(
mapStateToProps1
)(Counter1);
const mapStateToProps2 = (state: CombinedState): Counter2State => state.counter2;
const ConnectedCounter2 = connect(
mapStateToProps2
)(Counter2);
app.router(
(api?: RouterAPI) => {
let { history } = api!;
return (
(
<Router history={history}>
<>
<Route path="/counter1" component={ConnectedCounter} />
<Route path="/counter2" component={ConnectedCounter2} />
</>
</Router>
)
)
}
);
app.start('#root');
$ npm run build