DVA框架 todolist

背景

dva是一个基于 reduxredux-saga 的数据流方案,dva 还额外内置了 react-router 和 fetch。俗话说,掌握一个框架的基础就是实现一个todolist。

准备

dva-cli
antd@3.x

安装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';

将下方的路由修改为:

<Route path="/" exact component={TodoPage} />

在routes文件夹下,新建TodoPage.js文件,我们的todolist的整个页面都在这个组件中。

2. 设计组件结构

拆分结构,拆出的组件放入components文件夹,todolist分为两部分,第一部分是上面的表单,由输入框和一个提交按钮组成,用于输入todo,将todo提交。
第二部分是下面的todolist的展示区域,可看到一条一条的todo组成的列表
所以我们拆分成TodoForm.jsTodoList.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 (
    <Form layout={'inline'} onSubmit={handleSubmit}>
      <Form.Item>
        {getFieldDecorator('todoData', {
          rules: [{ required: true, message: '请输入你要做的todo!' }],
        })(
          <Input
            prefix={<Icon type="snippets" style={{ color: 'rgba(0,0,0,.25)' }} />}
            // placeholder="Username"
          />,
        )}
      </Form.Item>
      <Form.Item>
        <Button type="primary" htmlType="submit">
          submit
        </Button>
      </Form.Item>
    </Form>
  );
};

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的组件,循环将数据展示出来。
下面是列表相关代码:

<List
    className={styles.list}
    bordered
    dataSource={list}
    loading={isLoading}
    renderItem={(item, index) => (
        <List.Item
          actions={[<Button type="link" onClick={() => handleDel(index)}>delete</Button>]}
        >
          <Checkbox onClick={() => handleCheck(index)} checked={item.isCompleted === true ? true : false}>
            <Text delete={item.isCompleted === true ? true : false}>{item.data}</Text>
          </Checkbox>
        </List.Item>
    )}
 />

其中,handleDel是删除todo的方法,handleCheck是完成todo的方法
新增的方法为handleAdd,绑定在TodoForm组件中:

<TodoForm onSubmit={(value) => 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

我们将dispatchprops中获取到,然后发送dispatchmodels

// 新增(异步)
  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的modelstate我们定义一个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
    }
},

还有个处理loadingreducer

'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

最后来个成品图

todolist,右侧可delete这条todo

理发师Tony 2021.3.6

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容