###简介
dva是阿里做的基于 redux、redux-saga 和 react-router 的轻量级前端框架
概念
API
- app = dva(opts) 创建应用,返回 dva 实例
- app.use(hooks) 配置 hooks 或者注册插件,中间件
- app.model(model) 注册 model 模型
- app.router(({ history, app }) => RouterConfig) 注册路由表
- app.start(selector?) 启动应用。selector 可选
他基于redux、redux-saga 和 react-router做了简单的封装,其实并没有什么新的东西
下面我们间的应用一下
首先需要安装 npm i dva -s
src/index.js
//connect react-redux
import React, { Component } from 'react';
import dva,{connect} from 'dva';
let app = dva();
// combineReducers({
// counter:counter
// });
//状态state是合并后的状态树
//配置模型
app.model({
//命令空间,它就是以前的combineReducers里面的key
namespace: 'counter',
state: {
number: 0
},
//这里可以定义子reducer,它可以而且只能由它来修改状态
reducers: {
//这个名字是有意义的,如果派发一个counter/add,就会执行此reducer
add(state,action){
return {number: state.number + 1}
},
minus(state, action) {
return { number: state.number - 1 };
//...state, current: state.current - 1
}
},
});
//在dva里面所有组件没有状态,都是参数,基本都用函数声明
function Counter({number,add}) {
return (
<div>
<p>{number}</p>
<button onClick={add}>+</button>
</div>
)
}
let actions = {
add() {
//dispatch({ type: 'counter/add' });
return { type: 'counter/add' };
}
}
let mapStateToProps = state => state.counter;
let ConnectedCounter = connect(
mapStateToProps,
actions
)(Counter);
//定义路由
app.router(({ history, app }) => (
<ConnectedCounter />
));
app.start('#root');
eg2 记录一个当前值和最大值 记录一S能点多少次
src/index.js
//connect react-redux
import React, { Component } from 'react';
import dva,{connect} from 'dva';
let app = dva();
//配置模型
app.model({
namespace: 'counter',
state: {
highest: 0,//最大值
current: 0//当前值
},
reducers: {
increment(state,action){
let newCurrent = state.current + 1;
return {
...state,
current : newCurrent,
// 新值大雨老得记录最大值就用新值,反之就用最大值
highest : newCurrent > state.highest ? newCurrent : state.highest
}
},
minus(state, action) {
//一秒之后自动减1
return { ...state,current: state.current - 1 };
//...state, current: state.current - 1
}
},
//这个对象里放的是副作用,放的是generator
// effects = redux-saga/effects
effects: {
*increment(action, { call, put }) {
//call表示调用一个异步任务
yield call(delay, 1000);
//在model里派发动作的话是不需要加counter或者说namespace前缀的
yield put({ type: 'minus' });
}
}
});
function delay(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});
}
//在dva里面所有组件没有状态,都是参数,基本都用函数声明
function Counter({highest, current, add}) {
return (
<div style={{ border: '1px solid red', textAlign: 'center', width: 200, height: 200, margin: '20px auto' }}>
<p>最大值:{highest}</p>
<p>当前值:{current}</p>
<button onClick={add}>+</button>
</div>
)
}
let actions = {
add() {
return { type: 'counter/increment' };
}
}
let mapStateToProps = state => state.counter;
let ConnectedCounter = connect(
mapStateToProps,
actions
)(Counter);
//定义路由
app.router(({ history, app }) => (
<ConnectedCounter />
));
app.start('#root');
dva生成项目
dva可以创建新项目 dva new name --demo
demo是自己版本,不加是正式版本
src/index.js
import React, { Component } from 'react';
import dva, { connect } from 'dva';
import { Router, Route, Switch, Link } from 'dva/router';
let todos = [{ id: 1, text: '1', completed: false }, { id: 2, text: '2', completed: true }];
let api = {
load() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(todos);
}, 1000);
});
},
add(todo) {
return new Promise((resolve) => {
setTimeout(() => {
todos = [...todos, todo];
resolve(todos);
}, 1000);
});
},
toggle(id) {
return new Promise((resolve) => {
setTimeout(() => {
todos = todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
});
resolve(todos);
}, 1000);
});
},
del(id) {
return new Promise((resolve) => {
setTimeout(() => {
todos = todos.filter(item => item.id != id);
resolve(todos);
}, 1000);
});
}
}
//路由器,规则,初始化
// 1. Initialize
const app = dva();
app.model({
namespace:'filter',
});
// 2. Model
app.model({
namespace: 'todos',
state: {
list: [],//todos的数据
filter: 'all'//过滤的类型
},
reducers: {
loaded(state, { list }) {
return {
...state,
list
}
},
changeFilter(state, { filter }) {
return {
...state,
filter
}
}
},
effects: {
*load(action, { put, call, select }) {
let list = yield call(api.load);
yield put({ type: 'loaded', list });
},
//切换ID对应的todo的完成状态
*toggle({ id }, { put, call, select }) {
let list = yield call(api.toggle, id);
yield put({ type: 'loaded', list });
},
*del({ id }, { put, call }) {
let list = yield call(api.del, id);
yield put({ type: 'loaded', list });
},
*add({ todo }, { call, put }) {
let list = yield call(api.add, todo);
yield put({ type: 'loaded', list });
}
},
//监听当url切换到todo的时候,当切过来的时候调用后台接口一步家在数据
subscriptions: {
//history操作历史 dispatch 派发动作
setup({ history, dispatch }) {
//当函数发生变化的时候会执行此回调函数,并传入location
history.listen(({ pathname }) => {
if (pathname == '/todos') {
//如果要执行一步任务的话肯定要把动作派发给effects
dispatch({ type: 'load' });
}
});
}
}
});
class Todos extends Component{
render() {
let { list, toggle, add, changeFilter, filter, del } = this.props;
return (
<div>
<input type="text" ref={input => this.text = input} />
<button onClick={() => { add(this.text.value); this.text.value = '' }}>增加</button>
<ul>
{
list.map(todo => (
<li>
<input type="checkbox"
onChange={() => toggle(todo.id)}
checked={todo.completed} />
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.text}</span>
<button onClick={del.bind(this, todo.id)}>删除</button>
</li>
))
}
</ul>
<div>
<button style={{ color: filter == 'uncompleted' && 'red' }} onClick={() => changeFilter('uncompleted')}>未完成</button>
<button style={{ color: filter == 'completed' && 'red' }} onClick={() => changeFilter('completed')}>已完成</button>
<button style={{ color: filter == 'all' && 'red' }} onClick={() => changeFilter('all')}>全部</button>
</div>
</div>
)
}
}
let actions = {
toggle(id) {
return { type: 'todos/toggle', id };
},
add(text) {
return { type: 'todos/add', todo: { id: Date.now(), text, completed: false } };
},
changeFilter(filter) {
return { type: 'todos/changeFilter', filter };
},
del(id) {
return { type: 'todos/del', id };
}
}
let WrappedTodos = connect(
state => ({
filter: state.todos.filter,
list: state.todos.list.filter(item => {
switch (state.todos.filter) {
case 'completed':
return item.completed;
case 'uncompleted':
return !item.completed;
default:
return true;
}
})
}),
actions
)(Todos);
// 3. Router
const HomePage = () => <div>Hello Dva.</div>;
//history是hashHistory和browserHistory的封装,是用来跳转路径的history.push() history.li
app.router(({ history }) =>
<Router history={history}>
<div>
<Link to="/">首页</Link>
<Link to="/todos">todos</Link>
<Switch>
<Route exact path="/" exact component={HomePage} />
<Route path="/todos" exact component={WrappedTodos} />
</Switch>
</div>
</Router>
);
// 4. Start
app.start('#root');
modal可以重复调用,我们把一个状态拆分出来
import ...
let todos...
let api = ...
const app = dva();
app.model({
namespace: 'filter',
state: 'all',
reducers: {
changeFilter(state, { filter }) {
return filter;
}
}
});
// 2. Model
app.model({
namespace: 'todos',
state: {
list: [],//todos的数据
// filter: 'all'//过滤的类型
},
reducers: {
loaded...
// changeFilter(state, { filter }) {
// return {
// ...state,
// filter
// }
// }
},
effects: ...
//监听当url切换到todo的时候,当切过来的时候调用后台接口一步家在数据
subscriptions: ...
}
class Todos ...
let actions = {
changeFilter(filter) {
// return { type: 'todos/changeFilter', filter };
return { type: 'filter/changeFilter', filter };
}
}
let WrappedTodos = connect(
state => ({
filter: state.filter,
list: state.todos.list.filter(item => {
switch (state.filter) {
...
}
})
}),
actions
)(Todos);
...
app.start('#root');
结合immutable
import React, { Component } from 'react';
import dva, { connect } from 'dva';
import { Router, Route, Switch, Link } from 'dva/router';
import immutable, { Map, fromJS } from 'immutable';
let todos = [{ id: 1, text: '1', completed: false }, { id: 2, text: '2', completed: true }];
let api = {
load() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(todos);
}, 1000);
});
},
add(todo) {
return new Promise((resolve) => {
setTimeout(() => {
todos = [...todos, todo];
resolve(todos);
}, 1000);
});
},
toggle(id) {
return new Promise((resolve) => {
setTimeout(() => {
todos = todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
});
resolve(todos);
}, 1000);
});
},
del(id) {
return new Promise((resolve) => {
setTimeout(() => {
todos = todos.filter(item => item.id != id);
resolve(todos);
}, 1000);
});
}
}
//路由器,规则,初始化
// 1. Initialize
const app = dva();
app.model({
namespace: 'filter',
state: 'all',
reducers: {
changeFilter(state, { filter }) {
return filter;
}
}
});
// 2. Model
app.model({
namespace: 'todos',
//state对象可以包一下
state: fromJS({list: []}),
reducers: {
loaded(state, { list }) {
// return {...state, list}
return fromJS({ list });
// return Map({ list: Map(list)});
},
// changeFilter(state, { filter }) {
// return {
// ...state,
// filter
// }
// }
},
effects: {
*load(action, { put, call, select }) {
let list = yield call(api.load);
yield put({ type: 'loaded', list });
},
//切换ID对应的todo的完成状态
*toggle({ id }, { put, call, select }) {
let list = yield call(api.toggle, id);
yield put({ type: 'loaded', list });
},
*del({ id }, { put, call }) {
let list = yield call(api.del, id);
yield put({ type: 'loaded', list });
},
*add({ todo }, { call, put }) {
let list = yield call(api.add, todo);
yield put({ type: 'loaded', list });
}
},
//监听当url切换到todo的时候,当切过来的时候调用后台接口一步家在数据
subscriptions: {
//实现钩子函数的作用
//history操作历史 dispatch 派发动作
setup({ history, dispatch }) {
//当函数发生变化的时候会执行此回调函数,并传入location
history.listen(({ pathname }) => {
if (pathname == '/todos') {
//如果要执行一步任务的话肯定要把动作派发给effects
dispatch({ type: 'load' });
}
});
}
}
});
class Todos extends Component{
render() {
let { list, toggle, add, changeFilter, filter, del } = this.props;
return (
<div>
<input type="text" ref={input => this.text = input} />
<button onClick={() => { add(this.text.value); this.text.value = '' }}>增加</button>
<ul>
{
list.map(todo => (
<li>
<input type="checkbox"
onChange={() => toggle(todo.id)}
checked={todo.completed} />
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.get('text')}</span>
<button onClick={del.bind(this, todo.get('id'))}>删除</button>
</li>
))
}
</ul>
<div>
<button style={{ color: filter == 'uncompleted' && 'red' }} onClick={() => changeFilter('uncompleted')}>未完成</button>
<button style={{ color: filter == 'completed' && 'red' }} onClick={() => changeFilter('completed')}>已完成</button>
<button style={{ color: filter == 'all' && 'red' }} onClick={() => changeFilter('all')}>全部</button>
</div>
</div>
)
}
}
let actions = {
toggle(id) {
return { type: 'todos/toggle', id };
},
add(text) {
return { type: 'todos/add', todo: { id: Date.now(), text, completed: false } };
},
changeFilter(filter) {
// return { type: 'todos/changeFilter', filter };
return { type: 'filter/changeFilter', filter };
},
del(id) {
return { type: 'todos/del', id };
}
}
let WrappedTodos = connect(
state => ({
filter: state.filter,
list: state.todos.get('list').filter(item => {
switch (state.filter) {
case 'completed':
return item.completed;
case 'uncompleted':
return !item.completed;
default:
return true;
}
})
}),
actions
)(Todos);
// 3. Router
const HomePage = () => <div>Hello Dva.</div>;
//history是hashHistory和browserHistory的封装,是用来跳转路径的history.push() history.li
app.router(({ history }) =>
<Router history={history}>
<div>
<Link to="/">首页</Link>
<Link to="/todos">todos</Link>
<Switch>
<Route exact path="/" exact component={HomePage} />
<Route path="/todos" exact component={WrappedTodos} />
</Switch>
</div>
</Router>
);
// 4. Start
app.start('#root');