使用react-redux实现todoList

使用react-redux实现todoList

一、Redux作用

统一状态管理,将分散在各个组件中的状态全部放在统一的store对象中进行管理,适用于状态特别分散、状态共用较多、状态传递较深的情形。

二、安装

1.创建项目

yarn init 

create-react-app todolist

2.安装redux

yarn add redux redux-chunk

3.目录结构规划

src | 
   -| commponents
   -| store

App.js
index.css
index.js
store | 
     -| index.js
     -| reducers.js
     -| actionCreators
     -| actionTypes
commponents | 
           -| Foot.jsx
           -| Item.jsx
           -| List.jsx
           -| Top.jsx

三、redux的说明

redux的原则:

(1)state 是只读的,唯一修改它的方式是 actions。

(2)更新的唯一方式:dispatch(action) -> reducer -> new state。

(3)Reducer 函数必须是“纯”的 —— 不能修改它的参数,也不能有副作用(不能对函数作用域之外的变量做任何更改。不要改变函数作用域以外的变量,不要调用其他会改变的函数,也不要dispatch actions等)

store对象的作用:

维持应用的 state;
提供 createStore(reducers) 方法 创建store;
提供 getState() 方法获取 state;
提供 dispatch(action) 方法更新 state;
通过 subscribe(listener) 注册监听器;
通过 subscribe(listener) 返回的函数注销监听器

redux改造todolist的效果

(1)将分散在各个组件的状态统一放在store全局对象上,这样在每个组件需要状态时可以直接引入store取出状态,避免了状态之间的多层传递

(2)对state的所有操作必须通过dispatch发出action给reducer,由reducer根据action的type对state进行操作并返回新的state,这使state的改变可容易追踪和可控

(3)因为store是全局的,因此子组件不需要组件向父组件开放过多的接口(不需要传入那么多的变量给prop)

四、代码编写

store/index.js

说明:全局状态对象store创建和导出,它需要提供一个返回 state 的函数——reducers

import {createStore, applyMiddleware, compose} from 'redux'
import reducers from './reducers'
import ReduxThunk from  'redux-thunk'

// 处理redux-thunk的兼容问题
const composeEnhancers =
    typeof window === 'object' &&
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;

const enhancer = composeEnhancers(
    applyMiddleware(ReduxThunk)
);

//创建store状态管理对象
const store = createStore(reducers, enhancer);
export default store;

store/actionTypes

说明:

(1)Action对象描述你想做出的改变或者将触发的事件,它必须带有一个type 属性,为action提供type描述(说明),约定action在reducers中的匹配,这个文件就是将所有的type定义为常量,避免编写时出错。

(2)actionCreators中定义的每个回调函数返回的action对象上都有一个type属性,用以标识每个action;

(3)在界面通过store.dispatch(action)(这里的action都是从actionCreators中引入的)派发到renducers时,renducers函数是通过actions.type来判断传过来的是哪个action.

export const GET_ALL_ITEM = 'get_all_item'; // 存TODO
export const CHANGE_TODO_ITEM = 'change_todo_item'; // 修改一条TODO的选中状态
export const DEL_TODO_ITEM = 'del_todo_item'; //删除一条TODO
export const ADD_TODO_ITEM = 'add_todo_item'; //添加一条TODO
export const DEL_FINISHED_TODO_ITEM = 'del_finished_todo_item'; //删除所有已经完成的TODO
export const IS_CHECKED_ALL_TODO_ITEM = 'is_checked_all_todo_item'; //删除所有已经完成的TODO

store/actionCreators.js

说明:

(1)这里定义了所有的action,通过回调函数的形式返回action对象,使用回调函数的目的是为了让界面组件可以调用

(2)每个action都有一个type属性,它定义了该action的类型;同时接受一个参数,是界面组件需要的状态,由action带给reducer

(3)界面上调用这里定义的回调函数(返回一个对象,对象的第一个属性type是,第二个参数是从界面接收的参数),通过store.dispatch(action)将action派发到reducers,reducres根据action.type属性对state进行相应处理。

(4)action充当了界面与reducers的桥梁,将界面中组件需要的数据和对数据进行的处理传给renducers,由reducers负责更新state。

import {
    GET_ALL_ITEM,
    CHANGE_TODO_ITEM,
    DEL_TODO_ITEM,
    ADD_TODO_ITEM,
    DEL_FINISHED_TODO_ITEM,
    IS_CHECKED_ALL_TODO_ITEM
} from './actionTypes'

// 1. 存Todo
export const getAllItemAction = (todos)=>({
    type: GET_ALL_ITEM,
    todos
});

// 2. 单个TODO选中与否
export const getChangeItemFinishedAction = (todoId, flag)=>({
    type: CHANGE_TODO_ITEM,
    todoId,
    flag
});

// 3. 单个TODO删除
export const getDelItemAction = (todoId)=>({
    type: DEL_TODO_ITEM,
    todoId
});

// 4. 添加一条记录
export const getAddItemAction = (todo)=>({
    type: ADD_TODO_ITEM,
    todo
});

// 5. 删除所有已经完成的记录
export const getRemoveFinishedItemAction = ()=>({
    type: DEL_FINISHED_TODO_ITEM
});

// 6. 全选与非全选
export const getIsCheckedAllAction = (flag)=>({
    type: IS_CHECKED_ALL_TODO_ITEM,
    flag
});

store/reducers

说明:

(1)reducer 的职责是接收当前 state 和一个 action 然后返回新的 state。

(2)状态state以及操作状态的方法都在这里,符合了数据和操作数据的方法在同一个文件的原则

(3)根据在界面组件中通过store.dispatch(action)派发过来的action.type,匹配到相应的操作状态的方法,并根据组件传入的 参数,对state进行相应操作,将当前state直接进行替换,并合并旧的state返回给store

(4)reducer是直接用新的state替换旧的state,而不是更新旧的state,就是说旧的state还是保留的;返回最新的state给界面组件,同时保留了新state和旧state存入store,使state可以回溯和方便进行时间旅行调试。

import {
    GET_ALL_ITEM,
    CHANGE_TODO_ITEM,
    DEL_TODO_ITEM,
    ADD_TODO_ITEM,
    DEL_FINISHED_TODO_ITEM,
    IS_CHECKED_ALL_TODO_ITEM
} from './actionTypes'


// 初始状态数据
const defaultState = {
   todos: [],
   finishedCount: 0
};

//根据action的type,对state进行相应操作,并返回新的state
export default (state = defaultState, action)=>{
    console.log(state, action);
     // 1. 存所有的Todo
    if(action.type === GET_ALL_ITEM){
         const  newState = JSON.parse(JSON.stringify(state));
         newState.todos = action.todos;
        return newState;
    }
    // 2. 选中与取消选中
    if(action.type === CHANGE_TODO_ITEM){
        const  newState = JSON.parse(JSON.stringify(state));
        // 1. 遍历
        let tempFinishedCount = 0;
        newState.todos.forEach((todo, index)=>{
            if(action.todoId === todo.id){
                todo.finished = action.flag;
            }
            if(todo.finished){
                tempFinishedCount += 1;
            }
        });
        // 2. 返回一个新的状态
        newState.finishedCount = tempFinishedCount;
        return newState;
    }
    // 3. 单个TODO删除
    if(action.type === DEL_TODO_ITEM){
        const  newState = JSON.parse(JSON.stringify(state));
        // 1. 遍历
        let tempFinishedCount = 0;
        newState.todos.forEach((todo, index)=>{
            if(action.todoId === todo.id){
                newState.todos.splice(index, 1);
            }
        });
        // 处理选中的
        newState.todos.forEach((todo, index)=>{
            if(todo.finished){
                tempFinishedCount += 1;
            }
        });
        // 2. 返回新状态
        newState.finishedCount = tempFinishedCount;
        return newState;
    }
    // 4. 添加一条记录
    if(action.type === ADD_TODO_ITEM){
        console.log(action);
        const  newState = JSON.parse(JSON.stringify(state));
        newState.todos.push(action.todo);
        return newState;
    }
    // 5. 删除所有已经完成的记录
    if(action.type === DEL_FINISHED_TODO_ITEM){
        const  newState = JSON.parse(JSON.stringify(state));
        let tempArr = [];
        newState.todos.forEach((todo, index)=>{
            if(!todo.finished){ // 没有完成的任务
                tempArr.push(todo);
            }
        });

        // 2. 返回新状态
        newState.finishedCount = 0;
        newState.todos = tempArr;
        return newState;
    }
    // 6. 全选与非全选
    if(action.type === IS_CHECKED_ALL_TODO_ITEM){
        const  newState = JSON.parse(JSON.stringify(state));

        // 6.1. 遍历
        let tempFinishedCount = 0;
        newState.todos.forEach((todo, index)=>{
            todo.finished = action.flag;
        });

        // 处理选中的
        newState.todos.forEach((todo, index)=>{
            if(todo.finished){
                tempFinishedCount += 1;
            }
        });

        // 6.2. 返回新状态
        newState.finishedCount = tempFinishedCount;
        return newState;
    }

    return state;
}

组件

说明:

(1)需要的数据直接从store中取

(2)为了保证state更新同步到界面必须通过store.subscribe()对state进行监听

(3)当前组件只是需要取出状态,而不涉及状态的操作,因为没有store.dispatch(action)的操作

List.jsx

import React, {Component} from 'react'
import Item from "./item"
import store from './../store'

export default class List extends Component {
    constructor(props){
        super(props);

        // 当前组件需要的数据从store中获取
        this.state = store.getState();
    }

    componentDidMount() {
        // 在生命周期中订阅store的改变,并调用setState在监听到状态改变时立刻更新当前组件的state
        //保证状态更新同步到组件界面更新
        //这是由于react不是数据双向绑定的(视图和状态),状态更新时需要手动更新视图
        store.subscribe(this._handleStoreChange);
    }

    _handleStoreChange = ()=>{
       //  当前组件的状态要根据store进行更新
       this.setState(store.getState());
    };


    render() {
        //这里的状态数据来源于store
        const { todos} = this.state;
        return (
            <ul className="todo-main">
                {
                    todos.map((todo, index)=>(
                        <Item todo={todo} key={index} />
                    ))
                }
            </ul>
        )
    }
}

Item.jsx

说明:

(1)数据来源:todo从父组件中传入;isShowDelBtn从store中获取;

(2)改变选中状态和删除一条todo都是对state进行操作,redux只能通过reducer对state进行操作,所以对这两个对state的操作都使用store.dispatch(action)将要执行的操作派发给reducer进行

import React, {Component} from 'react'
import PropTypes from "prop-types";

import store from './../store'
import {getDelItemAction, getChangeItemFinishedAction} from './../store/actionCreators'

export default class Item extends Component{
    static propTypes = {
        todo: PropTypes.object.isRequired
    };

    constructor(props){
        super(props);

        this.state = {
            isShowDelBtn: false
        }
    }

    // 控制删除按钮的显示和隐藏
    _hasShowBtn(flag){
        this.setState({
            isShowDelBtn: flag
        })
    }
    
    //从actionCreators引入需要的store并传入当前组件需要的参数
    //通过store.dispatch(store)派发action到reducers
    //action会自动触发reducers,由reducers根据action进行状态更新,并由store将数据返回到组件
    _dealChange(todoId, flag){
        const action = getChangeItemFinishedAction(todoId, flag);
        store.dispatch(action);
    }

    _dealRemove(todoId){
        const action = getDelItemAction(todoId);
        store.dispatch(action);
    }

    render() {
        //状态来源于父组件List
        const { todo } = this.props;
        //状态来源于store
        const { isShowDelBtn } = this.state;
        
        return (
            <li
               onMouseOver={()=> this._hasShowBtn(true)}
               onMouseOut={()=> this._hasShowBtn(false)}
            >
                <label>
                    <input type="checkbox" checked={todo.finished} onChange={()=> this._dealChange(todo.id, !todo.finished)}/>
                    <span>{todo.title}</span>
                </label>
                <button
                    className="btn btn-warning"
                    style={{display: isShowDelBtn ?  "block" : "none"}}
                    onClick={()=>this._dealRemove(todo.id)}
                 >
                    删除
                </button>
            </li>
        )
    }
}

效果

使用react-redux实现todoList_第1张图片

查看github代码:github地址

你可能感兴趣的:(react,redux实现todoList,redux,todoList,react-redux)