使用dva@2和antd构建todo应用

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

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

推荐阅读更多精彩内容