背景
dva是一个基于 redux 和 redux-saga 的数据流方案,dva 还额外内置了 react-router 和 fetch。俗话说,掌握一个框架的基础就是实现一个todolist。
准备
dva-cli
[email protected]
安装dva-cli
npm install dva-cli -g
创建项目
dva new dva-study
使用antd
npm install antd babel-plugin-import --save
编辑 .webpackrc,使 babel-plugin-import 插件生效
{
"extraBabelPlugins": [
["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
]
}
过程
1. 定义路由组件
在router.js
中先引入我们要做的todo组件
import TodoPage from './routes/TodoPage';
将下方的路由修改为:
在routes文件夹下,新建TodoPage.js
文件,我们的todolist的整个页面都在这个组件中。
2. 设计组件结构
拆分结构,拆出的组件放入components
文件夹,todolist分为两部分,第一部分是上面的表单,由输入框和一个提交按钮组成,用于输入todo,将todo提交。
第二部分是下面的todolist的展示区域,可看到一条一条的todo组成的列表
所以我们拆分成TodoForm.js
和TodoList.js
两个文件,放入components
文件夹,TodoList.js
为主todo的架子,TodoForm
引入TodoList.js
中,作为子组件。
3. TodoForm
TodoForm主要又Form
表单构成,这个组件只是一个数据的提交,我们把提交出去的数据利用props
传递给父组件
import React from 'react';
import { Form, Input, Icon, Button } from 'antd';
const TodoForm = (props) => {
const handleSubmit = (e) => {
e.preventDefault();
props.form.validateFields((err, values) => {
if (!err) {
props.onSubmit(values);
props.form.resetFields();
}
});
};
const { getFieldDecorator } = props.form;
return (
{getFieldDecorator('todoData', {
rules: [{ required: true, message: '请输入你要做的todo!' }],
})(
}
// placeholder="Username"
/>,
)}
);
};
export default Form.create({name: 'todo_submit'})(TodoForm);
props.onSubmit(values)
主要是将onSubmit
方法传递给props
,这样父组件可以拿到onSubmit
方法。
props.form.resetFields()
是在提交一个todo后,清空input
用的。
4. TodoList
将TodoForm.js
引入TodoList.js
中,使用antd
中的List
的组件,循环将数据展示出来。
下面是列表相关代码:
(
handleDel(index)}>delete]}
>
handleCheck(index)} checked={item.isCompleted === true ? true : false}>
{item.data}
)}
/>
其中,handleDel
是删除todo的方法,handleCheck
是完成todo的方法
新增的方法为handleAdd
,绑定在TodoForm
组件中:
handleAdd(value)}/>
(这个组件在刚才List
组件上面)
以为我们要在这个页面,从models
中取state
,所以我们要利用connect
方法,将models
中的state
传入到组件中的props
中,即:mapStateToProps
方法,下面是相关代码:
const mapStateToProps = (state) => { // 这里的state的是从models中获取到的
// console.log(state);
return {
todo: state.todo // 将state中的"todo"引入(model中的namespace定义的名字为"todo")
}
}
export default connect(mapStateToProps)(TodoList) // 通过connect方法连接model
我们将dispatch
从props
中获取到,然后发送dispatch
给models
:
// 新增(异步)
const handleAdd = ({ todoData }) => {
dispatch({
type: 'todo/addASync',
payload: todoData
})
}
// 删除
const handleDel = (id) => {
dispatch({
type: 'todo/del',
payload: id
})
}
// 完成
const handleCheck = (id) => {
dispatch({
type: 'todo/check',
payload: id
})
}
为了使用到models
中的effects
方法,这里的新增todo的方法我们用了异步的增加,即点击之后,过一段时间才增加一条todo,这中间的延迟是我们模拟出来的,为了营造更加真实的网络环境,也为了练习dva
。
5. models
其实我们在开发的过程中,用到models
时,我们是需要先创建一个model
的,但是文章中为了条例更加通顺,我就按照大致的文件,一步一步来记录,实际过程中,会model
和组件之间,来回切换开发。
model
中我们创建一个namespace
为todo的model
,state
我们定义一个list
数组,默认值为空(刚开始为空数组,因为没有数据),和一个todo是否完成的isLoading
,默认值为false
。
Effects
代表的是副作用,可以理解为,我们拿到了数据,可以在这里面进行一些操作,比如处理异步,收发请求,可以在这里调用reducer
来改变state
,但Effects
是不能改变state
的,只有reducer
才能改变state
。
比如下面这段代码:
effects: {
* addASync({ payload }, { call, put }) { // eslint-disable-line
yield put({ type: 'loading', payload: true });
yield call(delay, 600);
yield put({ type: 'add', payload: payload });
yield put({ type: 'loading', payload: false });
},
},
其中,put
方法是调用reducer
,这里是调用的名为loading的reducer
,这个reducer
是为了改变state
中的isLoading
字段的,用来处理当前的loading状态,所以可以看到,第一个yield
是进入loading状态,最后一行的yield
是停止loading,即我们中间做完了操作后,停止loading,非常的合理。
中间的第二行,是为了模拟600毫秒的延迟,用了call
方法,delay
是从dva/saga
里面导入的。
第三行是调用了add
名字的reducer
,这个reducer
拿到值了以后,加入到state
中的list
数组中,push
进去,这样todolist就增加了一条,下面是这个reducer
的主要工作:
'add'(state, { payload: data }) { // payload字段是从组件中拿过来的,data就是我们要添加的内容,前面的state就是我们model中的state
// console.log(state);
state.list.push({
data: data,
isCompleted: false // 由于刚开始加入的todo还都未完成,所以我们这里的值是false
});
return {
list: state.list // 返回操作后的list
}
},
同样的道理,我们在删除
、完成
这两个操作中,我们的方式和增加
是类似的,下面是代码:
'del'(state, { payload: id }) {
state.list.splice(id, 1);
return {
list: state.list
}
},
'check'(state, { payload: id }) {
// console.log(state.list);
state.list.forEach((item, index) => {
if(index === id) {
state.list[index].isCompleted = !state.list[index].isCompleted;
}
})
return {
list: state.list
}
},
还有个处理loading
的reducer
:
'loading'(state, { payload: status }) {
// console.log(state);
state.isLoading = status;
return {
list: state.list,
isLoading: state.isLoading
}
}
总结
可以看到,整个的数据流非常的清晰,dva
为我们提供了非常好理解,便捷的方法去实现redux数据流,我们可以很清楚的知道我们在干什么,所有的数据流都在model
层,不会涉及到组件中,这样就把组件的view层和data很好地拆分出来。我们在model
层改变了state
,数据直接会同步到我们的组件当中,非常的beautiful
,非常的便捷。
目前没有在项目中使用mock
,因此services
这个文件夹整个下来就没有动过... 有时间把mock
也整进来,这样就完美了,不然每次刷新浏览器后,我的todo都没了...555
最后来个成品图
理发师Tony 2021.3.6