dva是基于redux,react-router,react-saga做的一层轻量封装,简化了redux中的action,reducer,store的配置,router的初始化,以及saga中的异步action处理,将上述的这些东西都统一在model中集中处理,使用起来非常方便;
使用dva脚手架需要你对redux,react-redux,redux-saga有一定的了解;
本文将通过一个todo应用的例子来呈现dva@2版本结合antd的使用,git地址:dva-2-todo,如果对你有帮助,请给个star,谢谢;
1.首先安装dva-cli
npm install dva-cli@next -g
dva -v
dva-cli version 1.0.0-bea.2
2.创建应用
dva new todo
cd todo
npm install
npm start
浏览器打开http://localhost:8000,应该能看到dva的欢迎页面;
3.生成todo路由
新建 src/pages/todo/page.js,代码如下:
export default () => (
<div>
todo
</div>
)
创建成功后打开http://localhost:8000/todo,会看到todo的输出;
4.创建todo应用的model
新增src/pages/todo/models/todo.js:
const delay = (timeout) => {
return new Promise((resolve) => {
setTimeout(resolve, timeout);
})
}
export default {
namespace: 'todo',
state: {
todos: [],
filter: null
},
reducers: {
save(state, { payload }) {
return {
...state,
todos: [
...state.todos,
payload
]
}
},
toggle(state, { payload }) {
return {
...state,
todos: state.todos.map(t => {
if(t.id === payload){
t.completed = !t.completed;
}
return t;
})
}
},
remove(state, { payload }) {
return {
...state,
todos: state.todos.filter(t => t.id !== payload)
}
},
setFilter(state, { payload }) {
return {
...state,
filter: payload
}
}
},
effects: {
*add({ payload }, { call, put }) {
yield call(delay, 500);
yield put({
type: 'save',
payload
})
}
},
subscriptions: {
setup({ dispatch, history }) {
return history.listen(({ pathname, query }) => {
if(pathname === "/todo") {
dispatch({
type: 'setFilter',
payload: query.filter || 'ALL'
})
}
})
}
}
}
namespace是当前model的命名空间,如要在组件内派发save类型的action,则action.type的值为"todo/save";
state字段为todo应用的初始state,todos为所有todo事项的集合,filter为过滤类型,分为'ALL','ACTIVE','COMPLETED'三种;
reducers里是处理不同action的reducer函数;
effects里是处理异步请求的函数,这里我们通过delay函数来模拟一个网络请求;
subscriptions用于订阅一个数据源,然后根据情况派发action,在这个例子中,监听浏览器路径,当进入todo后, 派发过滤的action;
此时在Redux开发者工具中就能看到todo的state:
5.创建UI组件
在src/pages/todo/components下,创建todo.js
todo.js:
import { connect } from 'dva';
import AddTodo from "./addTodo";
import TOdoList from "./todoList";
import Filter from "./filter";
function Todo(props) {
const {todos, filter} = props
return (
<div>
<AddTodo />
<TOdoList todos={todos}/>
<Filter filter={filter}/>
</div>
)
}
function getVisibleTodos(todos, filter) {
switch(filter) {
case 'ALL':
return todos;
case 'ACTIVE':
return todos.filter(t => !t.completed);
case 'COMPLETED':
return todos.filter(t => t.completed);
default:
return todos;
}
}
const mapStateToProps = ({todo}) => {
return {
...todo,
todos: getVisibleTodos(todo.todos, todo.filter)
}
}
export default connect(mapStateToProps)(Todo);
todo.js中包括AddTodo,TodoList,Fliter三个组件,分别创建这三个js
addTodo.js:
import { connect } from 'dva';
import { Form, Input, Button } from "antd";
const FormItem = Form.Item;
let id = 0;
function addTodo(props) {
function handleSubmit(e) {
e.preventDefault();
props.form.validateFields((err, values) => {
if (!err) {
props.dispatch({
type: 'todo/add',
payload: {
id,
text: values.todo,
completed: false
}
});
id++;
// reset form
props.form.resetFields();
}
});
};
const { getFieldDecorator } = props.form;
return (
<Form layout="inline" onSubmit={handleSubmit}>
<FormItem>
{getFieldDecorator('todo', {
rules: [{ required: true, message: 'Please input todo item!' }],
})(
<Input placeholder="todo" />
)}
</FormItem>
<FormItem>
<Button type='primary' htmlType='submit'>Add Todo</Button>
</FormItem>
</Form>
)
}
const mapStateToProps = ({todo}) => {
return {
todo
}
}
export default connect(mapStateToProps)(Form.create()(addTodo));
todoList.js:
import { connect } from 'dva';
import { List } from "antd";
function todoList(props) {
const { todos, dispatch } = props;
function handleToggle(id) {
dispatch({
type: 'todo/toggle',
payload: id
})
}
function renderItem(item) {
return (
<List.Item actions={[<a onClick={() => handleToggle(item.id)}>toogle</a>, <a>delete</a>]}>
<div>{item.text} - {item.completed ? "已完成" : "未完成"}</div>
</List.Item>
)
}
return (
<List
header={<h3>Todos:</h3>}
dataSource={todos}
renderItem={renderItem}
>
</List>
)
}
export default connect()(todoList);
filter.js:
import { connect } from 'dva';
function filter(props) {
const { filter, dispatch } = props;
function handleChange(_filter) {
dispatch({
type: 'todo/setFilter',
payload: _filter
})
}
function addFilterItem(_filter, text) {
if(_filter === filter){
return text;
}
return (
<a onClick={() => { handleChange(_filter) }}>{text}</a>
)
}
return (
<div>
<strong>SHOW:</strong>
{' '}
{ addFilterItem("ALL", "SHOW_ALL") }
{' | '}
{ addFilterItem("ACTIVE", "SHOW_ACTIVE") }
{' | '}
{ addFilterItem("COMPLETED", "SHOW_COMPLETED") }
</div>
)
}
export default connect()(filter);
6.处理loading
现在todolist的基本功能已经实现,由于添加todo是模拟异步fetch请求,所以接下来处理一下loading状态;
修改src/pages/todo/models/todo.js,具体参考这个commit;
ok,现在这个todo应用已经搞定了,在经过redux里一堆action,reducer难以组织的摧残之后,dva不要太好用,哈哈。
如果有问题,可以联系微信:duwenbin0316