react-redux实现todolist功能

一、redux介绍

        2014年 Facebook 提出了 Flux 架构的概念,引发了很多的实现。但是使用中发现很多不完美的地方,2015年,Redux 出现将 Flux 与函数式编程结合一起,关系如下:

1、引入redux

yarn add redux

2、store

即store/index.js,该导出的store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。当前时刻的 State,可以通过store.getState()拿到。通常如下结构:

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

/**
 * 第二个参数是调试google浏览器的调试插件redux-devtools,可以不要
 * @type {Store<unknown, Action>}
 */
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
export default store

3、reducer

reducer专门用户数据的处理,他不能直接修改state的数据,需要通过拷贝state改变数据,然后return返回该对象。

const newStatus = JSON.parse(JSON.stringify(state));

即如上只能修改newStatus的数据。通常结构如下:

const defaultStatus = {
    inputValue: "",
    list: [1, 2, 3]
}

//reducer可以接收status,但是不可以修改status,这就是拿到status为什么不修改,而是直接拷贝
export default (status = defaultStatus, action) => {
    //在这里修改数据
    const newStatus = JSON.parse(JSON.stringify(status));
    return newStatus;
}

defaultStatus是源数据对象,也是默认的数据。

4、action

在上面中可以看到有个参数action,要发送多少种消息,就会有多少种 Action,一个action表示一种行为数据,在view变化的数据都会通过store.dispatch(action)发送到这块,统一在这里处理。如果都手写,会很麻烦。可以可以定义一个函数来生成 Action,这个函数就叫 actionCreators,后面会介绍。

5、store.dispatch

store.dispatch用来发送view变化的数据,例如,监听input输入框输入的源数据,将数据发送到action处理

onChange={(e) => {
       const action = {
           inputValue: e.target.value,
           type: "input_value"
        }
        store.dispatch(action)
}}

6、store.subscribe

注册数据监听函数,一旦 State 发生变化,就自动执行这个函数,这时候可以在App的该函数中设置数据。

    constructor(prop) {
        super(prop);
        this.state = store.getState();
        //订阅了store.subscribe,只要store发生改变,handleStoreChange就会被调用
        store.subscribe(this.handleStoreChange);
    }
    handleStoreChange() {
        this.setState(store.getState());
    }

7、redux三大原则:

1、store必须是唯一的。
2、只有store可以改变自己的内容。
3、reducer必须是纯函数。即:给定输入,必定有输出,没有其他副作用。

关系如下:

二、redux实现todolist

1、引入antd和redux

$ yarn add antd

$ yarn add redux

2、创建store

store包含

actionTypes.js:action列表,类似于Java中的枚举类。

actionCreators.js:创建action,类似于Java中的Factory模式。

index.js:中间人,用于传递数据,是view和reducer的桥梁。

reducer.js:实际的数据处理类。接收数据——>处理数据——>返回数据。

(1)actionTypes

//输入框改变数据的type
export const CHANGE_INPUT_VALUE= 'change_input_value';
//添加数据的type
export const ADD_TODO_ITEM= 'add_todo_item';
//删除数据的type
export const DELETE_TODO_ITEM= 'delete_todo_item';

(2)actionCreators

import {ADD_TODO_ITEM, CHANGE_INPUT_VALUE, DELETE_TODO_ITEM} from "./actionTypes";

//获取数据框改变的action
export const getInputChangeAction = (value) => ({
    type: CHANGE_INPUT_VALUE,
    value
});

//获取添加数据action
export const getAddTodoItem=()=> ({
    type: ADD_TODO_ITEM
})

//删除数据的action
export const getDeleteTodoItem = (index) => ({
    type: DELETE_TODO_ITEM,
    value: index
})

(3)index

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

/**
 * 第二个参数是调试google浏览器的调试插件redux-devtools,可以不要
 * @type {Store<unknown, Action>}
 */
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
export default store

(3)reducer

import {CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM} from './actionTypes'

const defaultStatus = {
    inputValue: "",
    list: [1, 2, 3]
}

//reducer可以接收status,但是不可以修改status,这就是拿到status为什么不修改,而是直接拷贝
export default (status = defaultStatus, action) => {
    if (action.type === CHANGE_INPUT_VALUE) {
        //数据的深拷贝
        const newStatus = JSON.parse(JSON.stringify(status));
        //修改拷贝的数据
        newStatus.inputValue = action.value;
        //将修改后的数据返回,返回给了store
        return newStatus;
    } else if (action.type === ADD_TODO_ITEM) {
        const newStatus = JSON.parse(JSON.stringify(status));
        newStatus.list.push(newStatus.inputValue);
        newStatus.inputValue = '';
        return newStatus;
    } else if ((action.type === DELETE_TODO_ITEM)) {
        const newStatus = JSON.parse(JSON.stringify(status));
        newStatus.list.splice(action.value, 1);
        return newStatus;
    }
    return status;
}

3、创建TodoList,订阅store.subscribe

import React, {Component} from 'react'
import 'antd/dist/antd.css';
import store from "./store";
import {getAddTodoItem, getDeleteTodoItem, getInputChangeAction} from "./store/actionCreators";
import TodoListUI from "./TodoListUI";

/**
 * redux三大原则:
 * 1、store必须是唯一的。
 * 2、只有store可以改变自己的内容。
 * 3、reducer必须是纯函数。即:给定输入,必定有输出,没有其他副作用。
 */
class TodoList extends Component {

    constructor(prop) {
        super(prop);
        this.state = store.getState();
        this.handleInputChange = this.handleInputChange.bind(this);
        this.handleStoreChange = this.handleStoreChange.bind(this);
        this.handleClick = this.handleClick.bind(this);
        this.handleItemClick = this.handleItemClick.bind(this)
        //订阅了store.subscribe,只要store发生改变,handleStoreChange就会被调用
        store.subscribe(this.handleStoreChange);
    }

    render() {
        return (
            <TodoListUI
                inputValue={this.state.inputValue}
                list={this.state.list}
                handleInputChange={this.handleInputChange}
                handleClick={this.handleClick}
                handleItemClick={this.handleItemClick}
            />
        )
    }

    /**
     * 监听输入框内容
     * @param e
     */
    handleInputChange(e) {
        const action = getInputChangeAction(e.target.value);
        store.dispatch(action);
    }

    handleStoreChange() {
        this.setState(store.getState());
    }

    handleClick() {
        if (this.state.inputValue === '') return;
        const action = getAddTodoItem()
        store.dispatch(action);
    }

    handleItemClick(index) {
        let action = getDeleteTodoItem(index)
        store.dispatch(action);
    }
}

export default TodoList;

4、创建TodoListUI

import React, {Component} from "react";
import {Button, Input, List} from "antd";

class TodoListUI extends Component {
    render() {
        return (
            <div style={{marginTop: 10, marginLeft: 20}}>
                <Input placeholder="todolist" style={{width: 300, marginRight: 10}} value={this.props.inputValue}
                       onChange={this.props.handleInputChange}/>
                <Button type="primary" onClick={(this.props.handleClick)}>提交</Button>
                <List
                    style={{marginTop: 10, width: 300, marginRight: 10}}
                    bordered
                    dataSource={this.props.list}
                    renderItem={(item, index) => (
                        //注意这块handleItemClick有一个参数index,需要用到箭头函数,没有参数不需要箭头函数
                        <List.Item onClick={() => {
                            this.props.handleItemClick(index)
                        }}>
                            {item}
                        </List.Item>
                    )}
                />
            </div>
        );
    }
}

export default TodoListUI;

三、redux的中间件

redux常用的中间件有两个redux-thunk和redux-saga,原理如下:

1、redux-thunk

(1)作用

redux-thunk是用于处理action和store之间的中间件,dispatch只能接受一个对象,通过redux-thunk中间件使其可以接受一个函数,以上面的todolist为例,在生命周期函数componentDidMount中请求网络初始化数据:

    componentDidMount() {
        axios.get('/inputlist.json').then(value => {
            const data = value.data;
            const action = {
                type: INIT_INPUT_VALUE,
                data
            }
            dispatch(action)
        });
    }

通过redux-thunk可以把网络请求转移到actionCreators.js里面处理。

(2)引入

yarn add redux-thunk

(3)使用

在store/index.js中添加,添加多个中间件的方法参考:https://github.com/zalmoxisus/redux-devtools-extension

import {createStore, applyMiddleware, compose} from "redux";
import reducer from "./reducer";
import thunk from 'redux-thunk';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(
    applyMiddleware(thunk),
);
const store = createStore(reducer, enhancer);
export default store;

在actionTypes.js中定一个type:

//初始化
export const TODO_INIT_ITEM = 'todo_init_item';

在actionCreators.js中网络请求

export const getInitItem = (data) => ({
    type: TODO_INIT_ITEM,
    data
})

export const getTodoList = () => {
    return (dispatch) => {
        //网络请求
        axios.get('/inputlist.json').then(value => {
            let action = getInitItem(value.data);
            dispatch(action)
        });
    }
}

在TodoList.js生命周期函数componentDidMount中:

    componentDidMount() {
        //只有使用了thunker时候这块才能为一个函数
        let action = getTodoList();
        store.dispatch(action);
    }

在reducer中出处理数据:

        case TODO_INIT_ITEM:
            newState.list = action.data;
            break;

2、redux-saga

redux-saga在单独的文件中进行网络请求数据派发给store,不同于redux-thunke在actionCreators.js网络请求。

(1)引入

yarn add redux-saga

(2)使用

在store/index.js中添加,参考:GitHub - redux-saga/redux-saga: An alternative side effect model for Redux apps

import {createStore, compose, applyMiddleware} from "redux";
import reducer from "./reducer";
import createSagaMiddleware from 'redux-saga'
import saga from './todoDoSagas'

/**
 * 在单独的文件中进行网络请求数据派发给store
 *
 * https://github.com/redux-saga/redux-saga
 */
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(
    //使用中间件
    applyMiddleware(sagaMiddleware),
);
const store = createStore(reducer, enhancer);
sagaMiddleware.run(saga);

export default store;

在actionTypes.js中定一个type:

//初始化
export const TODO_INIT_ITEM = 'todo_init_item';
//请求
export const TODO_REQUEST_ITEM = 'todo_request_item';

创建todoDoSagas.js的文件夹,进行处理:

import {put, takeLatest,takeEvery} from 'redux-saga/effects'
import {TODO_INIT_ITEM, TODO_REQUEST_ITEM} from "./createTypes";
import axios from "axios";

function* todoSagas() {
    yield takeEvery(TODO_INIT_ITEM, initItem);
}

function* initItem() {
    try {
        const item = yield axios.get('/inputlist.json');
        //注意这个type类型和takeEvery是不一样的,写成一样的会导致请求进入死循环
        const action = {
            type: TODO_REQUEST_ITEM,
            data: item.data
        };
        yield put(action)
        console.log('请求成功')
    } catch (e) {
        console.log('请求失败');
    }
}

export default todoSagas;

在actionCreators.js中网络请求,注意这块的type类型

export const getTodoList = () => ({
    type:TODO_INIT_ITEM
})

在TodoList.js生命周期函数componentDidMount中:

    componentDidMount() {
        let action = getTodoList();
        store.dispatch(action)
    }

在reducer中出处理数据:

        case TODO_REQUEST_ITEM:
            //这块的类型和todoSagas的action一样,和App.js不一样
            newState.list = action.data;
            break;

目录如下:

效果如下:

最后附上todoList项目四次改造的四个源码:todolist.zip-Web开发文档类资源-CSDN下载

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值