Redux数据流框架介绍,构建易维护的单向数据流

React本身是一个View层,尽管借助prop属性和事件,能够完成父子组件之间的通信,但是在一个逻辑复杂的系统里,往往DOM的层次很深,如果每一层都传递一遍prop,开发效率就会有所下降。

这个时候,我们需要有一个专门管理数据流的框架来处理——Redux,通过Store/Dispatch/Reducers等概念,将整个数据流进行抽象。除此之外,还能加一些中间件(middleware)来进行拦截操作。下面我整理一下我使用Redux以来的一些知识点

1, 安装Redux依赖

基础框架可以使用我的开源仓库https://github.com/tun100/webpack-configure-react,并且在package.json里添加以下依赖,并且执行命令cnpm i -S -D

    "react-redux": "^5.0.5",
    "redux": "^3.7.2",
    "redux-saga": "^0.15.6",

2,最简单的Redux配置

首先,在import顶部增加以下代码

import React, { useEffect, useState, useContext, PureComponent, Component } from 'react';
import ReactDOM from 'react-dom';
import { Provider, connect } from "react-redux";
import { createStore, combineReducers, applyMiddleware, compose } from "redux";

然后初始化Redux,非常简单只需要几行代码,我们最终需要拿到的就是store这个变量,相关代码和注释如下

// Action静态变量定义
const ACTION_TYPE = {
    CHANGE_FOLLOW_NUMBER: "CHANGE_FOLLOW_NUMBER"
}
// Store的默认值
let initStore = {
    userinfo: {
        username: 'admin',
        followNumber: 0,
    }
}
// 纯函数Reducer,接收action并返回新State
function reducer (state = initStore, action) {
    console.log(`Reducer函数接收新Action:`, action, `当前State:`, state)
    switch (action.type)
    {
        case ACTION_TYPE.CHANGE_FOLLOW_NUMBER:
            // 此处进行浅拷贝,是为了State能够进行更新
            state.userinfo = {
                ...state.userinfo,
                followNumber: action.payload.value
            }
            break;
        case "@@redux/INIT":
            // 初始化的时候,Redux会传入该type
            break;
        default:
            // 如果无Action,则直接返回,不更新state
            console.log(`接收到未知Action,此处进行丢弃处理`)
            return state;
    }
    // 有更新时,需要进行基础的浅拷贝,让state下各个value指向新的地址
    return { ...state };
}
// 借助createStore生成store实例
const store = createStore(reducer);

如上所示,处理逻辑的是reducer函数,这个reducer在初始化或渲染的时候会被触发。

在触发的时候,该函数会接收到两个传参:

  • 第一个传参是state,初次使用会从默认值里获取,该参数表示当前Redux应用的实际状态值
  • 第二个传参是action,用来表示当前操作的type(类型)和payload(
    载荷 )是什么,往往项目会采用统一的规范:{type: ACTION_TYPE.XXXX,payload:{value:1}}

而且第二个传参action里的type,通常都会用一个静态常量来定义,例如上图的ActionType,这样做能够规范常量的使用,具体哪一些ActionType都一目了然

也许你会发现,修改userinfo.followNumber的时候我会进行浅拷贝操作,将原有object都转移到新state里。这是因为Redux默认判断就是根据浅拷贝比较(ShadowCompare),如果需要自定义比较方式,可以修改connect方法option里的 areStatePropsEqual/
areMergedPropsEqual等

3,使用Redux的Store

初始化Store之后,就需要借助Provider和connect将state里的值绑定进App了,代码和注释如下:

// 此处采用了ES新特性——装饰器,也可以改为原来hoc高阶函数connect(App)(...)的方式
// 此处传递了两个参数,第一个是mapStateToProp,第二个是mapDispatchToProp
// 其实作用是将state里的值,传递到子组件的props里,可以进行绑定和触发dispatch
@connect(state => ({
    userinfo: state.userinfo
}), (dispatch) => {
    return {
        changeFollowNumber (vector) {
            dispatch({
                type: ACTION_TYPE.CHANGE_FOLLOW_NUMBER,
                payload: {
                    value: vector
                }
            })
        }
    }
})
class App extends React.Component {
    render () {
        console.log(`App接收Redux更新,执行render方法`)
        let { userinfo } = this.props;
        return <div>
            <div>用户名: { userinfo.username }</div>
            <div>关注数: { userinfo.followNumber }</div>
            <div>
                <button type='button' onClick={ this.getChangeFollowNumberFnByVector(1) }>增加</button>
                <button type='button' onClick={ this.getChangeFollowNumberFnByVector(-1) }>减少</button>
            </div>
        </div>
    }
    // 此处根据传参,返回一个新事件给Button
    getChangeFollowNumberFnByVector (vector) {
        return () => {
            this.props.changeFollowNumber(this.props.userinfo.followNumber + vector)
        }
    }
}

// 此处初始化Provider,需要传递store
ReactDOM.render(
    <Provider store={ store }>
        <App />
    </Provider>,
    document.querySelector('#root')
);

到此处,基本的Redux框架就已经搭建好了。如果访问本机http://localhost:1234/index/index.html,则可以看到以下界面,一个用户名为admin,且数量为0,通过增加和减少按钮可以动态修改followNumber值

初次加载,可以看到Redux已经集成到React项目了
点击增加/减少按钮,可以看到Reducer函数会接收到App触发的dispatch请求,然后根据action.type和payload,来进行state更新

代码全览

import React, { useEffect, useState, useContext, PureComponent, Component } from 'react';
import ReactDOM from 'react-dom';
import { Provider, connect } from "react-redux";
import { createStore, combineReducers, applyMiddleware, compose } from "redux";

// Action静态变量定义
const ACTION_TYPE = {
    CHANGE_FOLLOW_NUMBER: "CHANGE_FOLLOW_NUMBER"
}
// Store的默认值
let initStore = {
    userinfo: {
        username: 'admin',
        followNumber: 0,
    }
}
// 纯函数Reducer,接收action并返回新State
function reducer (state = initStore, action) {
    console.log(`Reducer函数接收新Action:`, action, `当前State:`, state)
    switch (action.type)
    {
        case ACTION_TYPE.CHANGE_FOLLOW_NUMBER:
            // 此处进行浅拷贝,是为了State能够进行更新
            state.userinfo = {
                ...state.userinfo,
                followNumber: action.payload.value
            }
            break;
        case "@@redux/INIT":
            // 初始化的时候,Redux会传入该type
            break;
        default:
            // 如果无Action,则直接返回,不更新state
            console.log(`接收到未知Action,此处进行丢弃处理`)
            return state;
    }
    // 有更新时,需要进行基础的浅拷贝,让state下各个value指向新的地址
    return { ...state };
}
// 借助createStore生成store实例
const store = createStore(reducer);


// 此处采用了ES新特性——装饰器,也可以改为原来hoc高阶函数connect(App)(...)的方式
// 此处传递了两个参数,第一个是mapStateToProp,第二个是mapDispatchToProp
// 其实作用是将state里的值,传递到子组件的props里,可以进行绑定和触发dispatch
@connect(state => ({
    userinfo: state.userinfo
}), (dispatch) => {
    return {
        changeFollowNumber (vector) {
            dispatch({
                type: ACTION_TYPE.CHANGE_FOLLOW_NUMBER,
                payload: {
                    value: vector
                }
            })
        }
    }
})
class App extends React.Component {
    render () {
        console.log(`App接收Redux更新,执行render方法`)
        let { userinfo } = this.props;
        return <div>
            <div>用户名: { userinfo.username }</div>
            <div>关注数: { userinfo.followNumber }</div>
            <div>
                <button type='button' onClick={ this.getChangeFollowNumberFnByVector(1) }>增加</button>
                <button type='button' onClick={ this.getChangeFollowNumberFnByVector(-1) }>减少</button>
            </div>
        </div>
    }
    // 此处根据传参,返回一个新事件给Button
    getChangeFollowNumberFnByVector (vector) {
        return () => {
            this.props.changeFollowNumber(this.props.userinfo.followNumber + vector)
        }
    }
}

// 此处初始化Provider,需要传递store
ReactDOM.render(
    <Provider store={ store }>
        <App />
    </Provider>,
    document.querySelector('#root')
);

文章总结

本文描述了如何搭建一个基础的Redux项目,以及Redux单向数据流的整体生命周期,主要依靠Reducer/Action来进行state的更新,借助connect从store里获取state的值

到这里Redux其实还没有完全讲解完,因为还涉及到很多好用的中间件(Redux-Saga/Redux-Thunk等),数据渲染上的控制(Immutable.js/PureComponent),以及combineReducer来拆分Reducer等。涉及知识点比较多,我后面再找空闲时间整理一下

文章对Redux的理解,主要来自平时官网教程和工作经验,如有疏漏不当之处,敬请谅解

发表评论

邮箱地址不会被公开。 必填项已用*标注