react+redux的升级版todoList

又是很久不写博客了,最近在用蚂蚁金服的ant-design-pro写毕设,写着写着写不下去了,很多东西都不理解,不得不说大神写出来的东西都是需要花学习成本的,或者底子好,对于React新手来说就有点难了。所以就老老实实的认真看了下Redux到底如何使用,在这里推荐一下自己最近在看的书,写的算是比较详细的:《深入React技术栈》。废话不多说,今天就分享下自己如何使用redux来实现一个todoList的,希望对想要用redux的你会有所帮助。
(为什么叫升级版呢?因为之前写过一个没有用redux的todoList

最终截图:

image.png

一. 项目目录结构

image.png

该项目使用react官方的create-react-app架构,每个目录可以根据自己的需求来划分。下面解释下每个目录的内容和功能。

public: 主要放静态资源(入口html文件,图片资源,JSON文件等);
src/component:不同的组件;
src/layouts:整个页面的基本架构,主要就是Nav,Footer,Content。Nav里面显示User和Notice的数据,Content中实现页面路由的切换,Footer固定不变;
src/redux:
--src/redux/configureStore:生成整个应用的store;
--src/redux/reducers:所有reducer的集合;
src/routes:页面的整体路由;
src/utils:自己封装的工具;
views:存放项目中所要展示的所有页面;
index:整个项目的入口文件;

二. 具体实现

1. 整个应用中store中应存储什么数据?

const initialState = {
    taskListData: {  //任务列表
        loading: false,
        error: false,
        taskList: [],
    }, 
    userData: {  //用户信息
        loading: false,
        error: false,
        user: {},
    },
    noticeListData: {  //通知列表
        loading: false,
        error: false,
        noticeList: [],
    },
    taskData: {  //任务详情,在详情页使用
        loading: false,
        error: false,
        task: {},
    }
};

2. reducer的分布:
每个state对应一个reducer,所以一共需要四个reducer,在src/redux/reducers中将所有的reducer合并,并且注意每个reducer的名字要和state同名:

/*redux/reducers.js*/
import { combineReducers } from 'redux';
import userReducer from '../component/User/indexRedux';
import noticeReducer from '../component/Notice/indexRedux';
import todoListReducer from '../views/TodoList/indexRedux';
import taskReducer from '../views/Detail/indexRedux';

export default combineReducers({
    userData: userReducer,
    noticeListData: noticeReducer, 
    taskListData: todoListReducer,
    taskData: taskReducer,
});

每个state都对应一个reducer,所以和state一样,reducer应在放在最顶级的父级组件的目录中,所以将taskListData的reducer放在src/views/TodoList中,其他同理,代码如下:

/*views/TodoList/indexRedux.js*/
const taskListData = {
    loading: true,
    error: false,
    taskList: []
};

//不同的action;
const LOAD_TASKLIST = 'LOAD_TASKLIST';
const LOAD_TASKLIST_SUCCESS = 'LOAD_TASKLIST_SUCCESS';
const LOAD_TASKLIST_ERROR = 'LOAD_TASKLIST_ERROR';
const ADD_TASK = 'ADD_TASK';
const UPDATE_TASK = 'UPDATE_TASK';
const DELETE_TASK = 'DELETE_TASK';

function todoListReducer (state = { taskListData }, action) {
    switch(action.type) {
        case LOAD_TASKLIST: {
            return {
                ...state,
                loading: true,
                error: false,
            }
        }

        case LOAD_TASKLIST_SUCCESS: {
            return {
                ...state,
                loading: false,
                error: false,
                taskList: action.payload,
            };
        }

        case LOAD_TASKLIST_ERROR: {
            return {
                ...state,
                loading: false,
                error: true
            };
        }

        case UPDATE_TASK: {
            const index = state.taskList.indexOf(
                state.taskList.find(task => 
                    task.id === action.payload.id));
            console.log(index);
            state.taskList[index].status = !state.taskList[index].status;
            return {
                ...state,
                taskList: state.taskList,
            };
        }

        case DELETE_TASK: {
            const index = state.taskList.indexOf(
                state.taskList.find(task => 
                    task.id === action.payload.id));
            state.taskList.splice(index, 1);
            return {
                ...state,
                taskList: state.taskList,
            };
        }

        case ADD_TASK: {
            let len = state.taskList.length;
            let index = len > 0 ? len - 1 : 0;
            let lastTaskId = index !== 0 ? state.taskList[index].id : 0; 
            state.taskList.push({
                id: lastTaskId + 1,
                name: action.payload.name,
                status: false,
            });
            return {
                ...state,
                taskList: state.taskList,
            }
        } 

        default: {
            return state;
        }
    }
}

export default todoListReducer;


3. action creator的分布:
每个动作都代表一个action,action由组件发出,所以将action creator单独一个文件,放在组件目录中。例如:ListItem组件的action creator:

/*ListItem/indexRedux.js*/
//处理更新任务状态后和删除任务后的taskList的状态;

const UPDATE_TASK = 'UPDATE_TASK';
const DELETE_TASK = 'DELETE_TASK';

//action creator,更新和删除任务
export function updateTask (task) {
    return dispatch => {
        dispatch({
            type: UPDATE_TASK,
            payload: task,
        });
    }
}

export function deleteTask (task) {
    return dispatch => {
        dispatch({
            type: DELETE_TASK,
            payload: task,
        });
    }
}

三. 如何将redux和组件连接

react-redux提供了connect方法,将state和action creator绑在组件上,然后在组价内部以props的方式获取。下面是TodoList页面的具体实现:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import List from '../../component/List';
import { loadTaskList } from '../../component/List/indexRedux';
import { updateTask, deleteTask } from '../../component/ListItem/indexRedux';
import { addTask } from '../../component/SubmitDialog/indexRedux';

class TodoList extends Component {

    render () {
        return (
            <List {...this.props} />
        );
    }
}

export default connect( state => {
    return {
        loading: state.taskListData.loading,
        error: state.taskListData.error,
        taskList: state.taskListData.taskList,
    };
}, dispatch => {
    return {
        loadTaskList: bindActionCreators(loadTaskList, dispatch),
        updateTask: bindActionCreators(updateTask, dispatch),
        deleteTask: bindActionCreators(deleteTask, dispatch),
        addTask: bindActionCreators(addTask, dispatch),
    };
})(TodoList);

connect方法有四个参数,这里主要说下前两个参数:

(1)mapStateToProps:参数为state,返回页面所需要的所有state;
(2)mapDispatchToProps:参数为dispatch,返回页面所要使用的异步回调函数。

眼明手快的你肯定看到了,我们从redux包中导出了bindActionCreators方法,该方法将dispatch和action creator绑定,用来触发action。

四. 异步的action creator如何触发呢?

因为每个action creator都是异步函数,我们传给组件的只是函数的声明,所以就要引入我们的中间件,只用在生成store时加入就行了:

/*redux/configureStore.js*/
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducers from './reducers';

const initialState = {
    taskListData: {
        loading: false,
        error: false,
        taskList: [],
    }, 
    userData: {
        loading: false,
        error: false,
        user: {},
    },
    noticeListData: {
        loading: false,
        error: false,
        noticeList: [],
    },
    taskData: {
        loading: false,
        error: false,
        task: {},
    }
};

let enhancer = applyMiddleware(thunk);

let store = createStore(
    reducers,
    initialState,
    enhancer,
);

export default store;

在上面的代码中thunk就是一个中间件,我们将引入的中间件传入applyMiddleware就可以了。

五. store在哪里传入组件呢?

我们肯定会想到,store在整个应用中都存在,所以应该在整个应用的最顶层,对于一般项目而言,当然就是最顶端的路由了:

import React, { Component } from 'react';
import {  BrowserRouter as Router, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import BasicLayout from '../layouts';
import store from '../redux/configureStore';

class RouterApp extends Component {
    render () {
        return (
            <Provider store={store}>
                <Router>
                    <Route path="/" component={BasicLayout} />
                </Router>
            </Provider>
        );
    }
}

export default RouterApp; 

Provider是react-redux的一个组件,作用就是用来将store传入整个应用。

基本要讲的就是这些内容,完整的项目请看github

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