美文网首页让前端飞前端开发笔记
通过复盘todo list小项目,学习react-redux

通过复盘todo list小项目,学习react-redux

作者: SampleTape | 来源:发表于2019-02-22 18:42 被阅读0次

前言

自学react的时候看的是程墨的《深入浅出React和Redux》,这本书很适合初学者,使用create-react-app创建项目,引用一句书中原话:

用这种最简单的方式创建可运行的应用,必要的时候才会介绍底层技术拢的细节,毕竟,没有什么比一个能运行的应用更加增强开发者的信心。

过多的配置会消磨掉学习者的大部分耐心,甚至失去学习react的兴趣,尽早开始实际的react开发是最高效的学习方法。

虽然很喜欢这本书,但是本文的todo list和《深入浅出React和Redux》中的实例不同,是我自己摸索着完全自行设计开发的,一条todo记录可以有3种状态,未开始(todo),正在进行(doing)和已完成(done)。当然,如果遇到问题,这本书一直都是我的参考书。

预备知识

《深入浅出React和Redux》前4章内容,react和redux相关知识这里不再赘述,本文适合"书里说的我都懂,但是从来没有动手实践过的仔"。

项目链接

这也是注册github这么久第一个阶段性完成的项目=(:з」∠)_
欢迎加星!
https://github.com/SampleTape/todolist/

项目截图

项目截图.PNG

组件划分

ToDoListPanel是最外层的容器组件,其中包含着Filter,List,AddDialog,而List中包含着多个Item。

  • Filter用于通过todo的状态进行过滤,是顶部的tab选项卡。
  • List就是todo列表,包含多个Item,目前作用是根据filter种类更换背景色。
  • Item是一条todo,可以更换todo状态,或删除本条todo。
  • AddDialog是下方新建一条todo时的对话框,只在用户点击"+"按钮时显示。
ToDoListPanel
  |_Filter
  |_List
      |_Item
  |_AddDialog

项目目录

项目目录.PNG

看着这么多文件,从哪里入手刚开始的确让人头疼。

1. 首先你需要在电脑中找到一个地方,使用上文提到的命令快速创建todolist项目文件夹,其中的大部分文件,这个指令会为你自动生成:

C:\Users\zuoy\Desktop\react> creact-react-app todolist

2. 既然用到react-redux,别忘了安装它:

C:\Users\zuoy\Desktop\react\todolist> npm install react-redux

3. 然后呢,我们可以先创建几个空文件夹和空文件:

  • components
  • images
  • styles
  • actions.js
  • actionTypes.js
  • reducer.js
  • store.js

以上就是这个项目中,我们需要集中精力的几个主要地方,已有的index.js稍后需要稍作修改,其中images中放图片,styles中放样式文件,这些我们先忽略,是不是比截图中看起来感觉轻松些(✿◡‿◡)

4. 接下来呢,从store.js开始吧,这个文件的主要作用是定义数据结构,并且通过createStore生成store,并且把reducer回调函数和初始值传给它,store中总是保存着最新鲜的数据:

import {createStore} from 'redux';
import reducer from './reducer';

const initialList = {
    todos: [
        {
            id: 0,
            what: 'dream',
            status: 'todo',
        }
    ],
    filter: 'todo',
    showadddialog: false,
    counter: 1,
};

const store = createStore(reducer, initialList);

export default store;

todos是个数组,用来储存todo记录,每个todo记录中包含id,what,status几个字段。filter表示筛选项,showadddialog用来控制添加新todo对话框的显示隐藏,counter表示todo记录计数器,将来会被赋值给id,只增加不减少。

5. 接下来要写的时actionTypes.js,用来定义action的种类,这里需要根据你设计的功能点来确定需要几种action。

export const ADD = 'add';

export const DELETE = 'delete';

export const START = 'start';

export const FINISHED = 'finished';

export const FILTER = 'filter';

export const SHOWADDDIALOG = 'showadddialog';

6. 既然actionTypes.js 都已经写好,现在开始写actions.js吧,这里需要确定不同action种类需要传递给reducer什么样参数,所以返回值除了包含type这个字段以外,还要包含传递的参数:

import * as ActionTypes from './actionTypes';

export const add = (what) => {
    return {
        type: ActionTypes.ADD,
        id: 0,
        what,
    };
};

export const deleteit = (id) => {
    return {
        type: ActionTypes.DELETE,
        id,
    };
};

export const start = (id) => {
    return {
        type: ActionTypes.START,
        id,
    };
};

export const finished = (id) => {
    return {
        type: ActionTypes.FINISHED,
        id,
    };
};

export const filter = (filter) => {
    return {
        type: ActionTypes.FILTER,
        filter,
    };
}

export const showAddDialog = (showadddialog) => {
    return {
        type: ActionTypes.SHOWADDDIALOG,
        showadddialog,
    }
}

7. 刚刚提到了reducer,现在我们开始写reducer.js,reducer负责根据action种类和参数更新state,记住不要直接修改state,而是返回一个新的对象:

import * as ActionTypes from './actionTypes';

export default (state, action) => {
    let newTodos = [...state.todos];
    let index = newTodos.findIndex(todo => todo.id === action.id);
    switch(action.type) {
        case ActionTypes.ADD:
            let todo = {
                id: state.counter,
                what: action.what,
                status: 'todo',
            };
            newTodos.push(todo);
            return {...state, todos: newTodos, counter: state.counter + 1};
        case ActionTypes.DELETE: 
            newTodos.splice(index,1);
            return {...state, todos: newTodos};
        case ActionTypes.START:
            newTodos[index].status = 'doing';
            return {...state, todos: newTodos};
        case ActionTypes.FINISHED:
            newTodos[index].status = 'done';
            return {...state, todos: newTodos};
        case ActionTypes.FILTER:
            return {...state, filter: action.filter};
        case ActionTypes.SHOWADDDIALOG:
            return {...state, showadddialog: action.showadddialog};
        default:
            return state;
    }
}

8. 基础已经打好了,可以开发组件了!

  • adddialog.js
  • filter.js
  • item.js
  • list.js
  • todolistpanel.js

8.1. item.js

与其他组件一样,其中包含着一个纯函数作为傻瓜组件(展示组件)。它不需要追踪最新的state所以mapStateToProps函数只返回一个空对象;当鼠标点击相应按钮会触发一个动作,这时需要dispatch一个action,使store更新状态,需要在mapDispatchToProps函数中定义一系列函数 ,这些方法会被传递给傻瓜组件使用;connect可以根据提供的参数将傻瓜组建转换成容器组建。

import React from 'react';
import * as Actions from '../actions';
import {connect} from 'react-redux';
import start from '../images/start.png';
import finished from '../images/finished.png';
import deleteit from '../images/deleteit.png';

import '../styles/item.scss';

function Item({id, what, startToDo, finishToDo,deleteToDo}) {
    return (
        <div className="Item">
            <div className="ItemAttribute text">
                {what}
            </div>
            <div className="ItemAttribute" onClick={startToDo}>
                <img src={start} alt="start" />
            </div>
            <div className="ItemAttribute" onClick={finishToDo}>
                <img src={finished} alt="finished" />
            </div>
            <div className="ItemAttribute" onClick={deleteToDo}>
                <img src={deleteit} alt="deleteit" />
            </div>
        </div>
    );
}

function mapStateToProps(state, ownProps) {
    return {};
}

function mapDispatchToProps(dispatch, ownProps) {
    return {
        startToDo: () => {
            dispatch(Actions.start(ownProps.id));
        },
        finishToDo: () => {
            dispatch(Actions.finished(ownProps.id));
        },
        deleteToDo: () => {
            dispatch(Actions.deleteit(ownProps.id));
        }
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(Item);

8.2. list.js

List这个组件与Item刚好相反,它仅读取state数据而不去修改它,所以mapDispatchToProps函数返回空对象,mapStateToProps函数返回对象中的字段将会传递给他的傻瓜组件。

import React from 'react';
import {connect} from 'react-redux';
import Item from './item';

import '../styles/list.scss';

function List({todos, filter}){
    return (
        <div className={`List ${filter}`}>
            {todos.map((todo, key) => {
                if (todo.status === filter) {
                    return (
                        <Item id={todo.id} what={todo.what} key={key}></Item>
                    );
                } else {
                    return null;
                }
            })}
        </div>
    );
}

function mapStateToProps(state) {
    return {
        todos: state.todos,
        filter: state.filter,
    };
}

function mapDispatchToProps(dispatch) {
    return {};
}

export default connect(mapStateToProps,mapDispatchToProps)(List);

8.3. filter.js

Filter有点特别, 他的傻瓜组件需要给容器组建传参,从而点击不同的按钮显示不同的todo记录。这个时候傻瓜组建就没有写成纯函数的形式,而是写成了标准的React组件,并且写了handleClick这个方法,通过点击事件目标对象的id号传递不同的参数给filter这个方法。

import React from 'react';
import {connect} from 'react-redux';
import * as Actions from '../actions';

import '../styles/filter.scss';

class Filter extends React.Component {
    constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick(e) {
        this.props.filter(e.target.id.split('-')[1]);
    }
    render() {
        return (
            <div className="TabTittle">
                <div className={`title ${this.props.filterName === 'todo' ? 'active' : ''}`}
                     id="filter-todo" 
                     onClick={this.handleClick}>To Do</div>
                <div className={`title ${this.props.filterName === 'doing' ? 'active' : ''}`}
                     id="filter-doing" 
                     onClick={this.handleClick}>Doing</div>
                <div className={`title ${this.props.filterName === 'done' ? 'active' : ''}`} 
                     id="filter-done" 
                     onClick={this.handleClick}>Done</div>
            </div>
        );
    }
}

function mapStateToProps(state, ownProps) {
    return {
        filterName: state.filter,
    };
}

function mapDispatchToProps(dispatch) {
    return {
        filter: function(status) {
            dispatch(Actions.filter(status));
        }
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(Filter);

8.4. adddialog.js

AddDialog这个组件也很特别,它需要自己暂存一个state,表示用户当前输入的内容,等待用户点击"OK"这个按钮才把数据传出去。所以在这个组件的定义中,你会看到傻瓜组建也在维护自己的state。

import React from 'react';
import * as Actions from '../actions';
import {connect} from 'react-redux';

import '../styles/adddialog.scss';

class AddDialog extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            what: '',
        };
        this.handleAddTODO = this.handleAddTODO.bind(this);
        this.handleWhatChanged = this.handleWhatChanged.bind(this);
        this.handleCancel = this.handleCancel.bind(this);
    }
    handleAddTODO() {
        this.props.addToDo(this.state.what);
        this.props.showAddDialog(false);
        this.setState({
            what: '',
        });
    }
    handleWhatChanged(e) {
        this.setState({
            what: e.target.value,
        });
    }
    handleCancel() {
        this.props.showAddDialog(false);
    }
    render() {
        return (
            <div className={`AddDialog ${this.props.showadddialog ? 'showAddDialog' : ''}`}>
                <div className="AddDialogTitle">Add</div>
                <textarea value={this.state.what} onChange={this.handleWhatChanged} />
                <div className="ButtonContainer">
                    <div className="CancelButton" onClick={this.handleCancel}>Cancel</div>
                    <div className="OKButton" onClick={this.handleAddTODO}>OK</div>
                </div>
            </div>
        );
    }   
}

function mapStateToProps(state) {
    return {
        showadddialog: state.showadddialog,
    };
}

function mapDispatchToProps(dispatch) {
    return {
        addToDo: function(what) {
            dispatch(Actions.add(what));
        },
        showAddDialog: function(show) {
            dispatch(Actions.showAddDialog(show));
        }
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(AddDialog);

8.5. todolistpanel.js

最后把所有组件组合到一起。

import React from 'react';
import {connect} from 'react-redux';
import * as Actions from '../actions';
import List from './list';
import AddDialog from './adddialog';
import Filter from './filter';
import '../styles/todolistpanel.scss';

class ToDoListPanel extends React.Component {
    constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick() {
        this.props.showAddDialog(true);
    }
    render() {
        return (
            <div className="ToDoListPanel">
                <Filter></Filter>
                <div className="TabContent">
                    <List></List>
                </div>
                <div className="addButton" onClick={this.handleClick}>
                    +
                    <div className="addButtonTriangle"></div>
                </div>
                <AddDialog></AddDialog>
            </div>
        );
    }
}

function mapStateTpProps(state) {
    return {};
}

function mapDispachToProps(dispatch) {
    return {
        showAddDialog: function(show) {
            dispatch(Actions.showAddDialog(show));
        }
    };
}

export default connect(mapStateTpProps, mapDispachToProps)(ToDoListPanel);

8.6 index.js

最后别忘了修改index.js,感谢Provider为我们提供的store。

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import ToDoListPanel from './components/todolistpanel';
import store from './store.js';
import * as serviceWorker from './serviceWorker';

import './index.css';

ReactDOM.render(
    <Provider store={store}>
        <ToDoListPanel />
    </Provider>
    , document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();

相关文章

  • 通过复盘todo list小项目,学习react-redux

    前言 自学react的时候看的是程墨的《深入浅出React和Redux》,这本书很适合初学者,使用create-r...

  • Day 5(Python + git + Flask + Mon

    学习Celery 每日任务: 在本地搭建一个todo list的Web项目,支持通过接口进行相关操作 支持通过接口...

  • Day 4(Python + Git + Flask + Mon

    学习Flask,了解如何搭建一个Web服务器 每日任务: 在本地搭建一个todo list的Web项目,支持通过接...

  • Day 4

    todo list web做成接口 学习celery的基本使用 todo list web文件上传功能 celer...

  • react-redux 初试之todo list

    最近在学react,官方的英文文档看起来甚是有意思,但 talk is cheap, show me the co...

  • sweetalert的封装

    todo list [ ] 结合sweetalert来学习项目的封装技术 [ ]代码和注释都来自知了课堂,由本人整...

  • 2018-10-10 DAY2 时间管理-知道做什么

    时间管理打卡作业-- 10月10日待办事项Todo List : 1.时间管理打卡提交 2.E战到底打卡任务+复盘...

  • 复盘(刘健)

    凡事皆可被复盘,人人皆可学复盘。通过复盘的系统方法,建立复盘的基础认知。应用于项目复盘、业务复盘、团队复盘、工作场...

  • Day 3

    学习virtualenv 搭建todo list web版本pythonflaskjinjan2MongoDBbo...

  • React实践项目--todolist(1)初始化配置

    本系列文章会展示如何使用 React 构建一个简单的 todo_list 项目。由于我们还没有学习 redux ,...

网友评论

    本文标题:通过复盘todo list小项目,学习react-redux

    本文链接:https://www.haomeiwen.com/subject/uedeyqtx.html