GX博客

分享个人 Full-Stack JavaScript 项目开发经验

使用react-redux作为Redux的Store与React组件的集成方案

本文将介绍怎么借助react-redux集成Redux的 Store 与 React 组件 的具体步骤。包括 Store 的创建、react-redux 的基本使用方式和 React 组件需要注意的问题等。为了化繁为简,突出它们三者的协作关系,本文先跳过哈希路由和异步请求等内容。


应用背景

React 的 State 管理会随着应用程序规模的扩张而变得繁琐,最好的做法是在 React 之外管理 State,确保大部分的 React 组件可以是无状态的 UI 组件。

这并不意味 React 组件不能再使用 State,而是在必要的情况下谨慎使用。

Redux介绍

Facebook 开发了一种可用于替代 MVC 的设计模式,Flux。它旨在保持数据的单向流动。Redux 就是其中一个流行的类 Flux 脚本库。

安装 Redux:

yarn add redux

创建 Redux 的开发目录:

src/
├── actionCreator/
├     ├── 数据节点A的Action生成器.js
├     ├── 数据节点B的Action生成器.js
├     ├── ......
├     └── 公共数据节点的Action生成器.js
├── actionTypes/
├     └── index.js
├── components/
├     ├── 模块A的UI组件/
├     ├── 模块B的UI组件/
├     ├── ......
├     └── 公共UI组件/
├── containers/
├     ├── 模块A的容器组件.js
├     ├── 模块B的容器组件.js
├     └── ......
├── reducers/
├     ├── index.js
├     ├── 数据节点A的reducer.js
├     ├── 数据节点B的reducer.js
├     └── ......
├── store/
├     ├── index.js
├     └── initialState.js
├── App.js
└── index.js

上面为在项目中创建一个基于 Redux 的源码目录 src,它最后会被打包到客户端运行。

  • 最外层 index.js

    它为 webpack 打包入口主文件。在这里获取初始化 Store 对象,应用 react-redux 和 react-router-dom 来封装 App 组件并 render 到 DOM 中。

  • App.js

    它为 React 应用程序的根组件,在这里分配路由到各个容器组件。

  • store/initialState.js

    它为应用程序的初始化 State 对象。要了解有关 State 范式化的说明,请点击这里

  • store/index.js

    它为构建 Store 的主文件。在这里将合成所有 reducers、传入初始化 State 和应用如 redux-thunk、redux-logger、redux-devtools-extension 等的中间件。

  • reducers/index.js

    reducers 是以模块化方式描述 State 对象的函数。它一般按数据节点拆分,然后通过 index.js 整合成一个导出对象。要了解有关 reducers 的拆分逻辑,请点击这里

  • containers/

    App 要使用的容器组件。容器组件即将 UI 组件和数据相连的组件。它们将映射组件属性到 State,并将回调函数属性传递给 Store 的 dispatch 方法,来达到渲染 UI 组件的目的。这些容器组件往往是一个功能模块或主菜单的顶级组件。

  • components/

    UI 组件,即只关注于 UI 渲染的组件,大多是无状态的。它们通过属性接收数据,并通过回调函数属性,以状态提升的方式把数据回传给容器组件。一般地,可以以功能模块或菜单为单位对 UI 组件进行划分。再抽取公共的 UI 组件,以组件合成的方式给各功能模块或菜单重用。

  • actionTypes/index.js

    它把应用程序所有的 Action 映射到一个常量对象的属性中。这样做的好处是,当 Action 拼写错误时,undefined 的 Action 能导致浏览器抛出错误。若是一个错误的字符串,则不会触发任何警告信息,只是 State 不会预期变更。同时这也方便编辑器的代码提示和自动补全,并且可以一览应用程序所有功能模块或菜单的 Action。

  • actionCreator/

    Action 是一个简单的 JavaScript 对象。它至少包含一个 type 属性,描述变更 State 的指令名称。它一般还包含执行变更必须要的数据。Action 告诉了我们变更的内容是什么,还有创建变更时使用到哪些数据。而 Action 生成器就是根据用户操作返回对应 Action 的一个函数。这个函数还可能包含服务器请求等的异步操作。最终通过 dispatch 发出这个 Action。一般地,actionCreator 对应着 reducers,以数据节点来拆分和维护。对于各个容器组件共享的数据节点,可以设置为一个公共数据节点 Action 生成器。


下面来具体介绍各个部分的代码实例。

1、分析应用程序需要使用到的 State 数据,包括用于展示的数据和 UI 控制的 State,并按照范式创建 store/initialState.js。

const initialState = {
    // 加载中(ui)
    loading: {
        show: false,
        lockPage: false
    },
    //(专题管理)
    topic: {
        isFetching: false,
        // ......
    },
    //(清理冗余)
    clear: {
        isFetching: false,
        // ......
    },
    // ......
};

export default initialState;

2、依据 State 结构和功能模块或菜单的划分,添加对应的 actionTypes:

const actionsTypes = {
    // state.topic (专题管理)
    FETCHING_TOPIC: 'FETCHING_TOPIC',
    // ......
    // state.clear (清理冗余)
    FETCHING_CLEAR: 'FETCHING_CLEAR',
    // ......
};

export default initialState;

3、根据 actionTypes 创建响应的 actionCreator,如 actionCreator/clear.js:

export function resetClear() {
    return {
        type: actionTypes.RESET_CLEAR,
        isFetching: false,
        clearSuccess: false
    }
}
// ......

4、根据 actionCreator 的返回 Action 创建对应 reducer,如 reducers/clear.js:

import actionTypes from '../actionTypes';

export function clear( state = { isFetching: false, clearSuccess: false }, action ) {
    switch (action.type) {
        case actionTypes.RESET_CLEAR: {
            return {
                isFetching: action.isFetching,
                clearSuccess: action.clearSuccess
            };
        }
        // ......
        default: {
            return state;
        }
    }
}

可以看出,reducer 接收当前 State 节点和 action 作为参数,并通过这些参数创建并返回一个新的 State 节点。


5、在 store/index.js 中创建 Store 的初始化方法:

import {combineReducers, createStore} from 'redux';
import initialState from './initialState';
import reducers from '../reducers';

const configureStore = () =>
    createStore(
        combineReducers(reducers),
        initialState
    );

export default configureStore;

为了简练,这里已把处理异步操作和调试的中间件省略。


6、在最外层 index.js 主入口文件中调用 Store 的初始化方法:

import configureStore from "./store";

const store = configureStore();

// store.dispatch(your_action_creator());
// store.getState();
// const unsubscribeA = store.subscribe(()={//...})
// unsubscribeA();

到此为止,Redux 的 Store 已经构建完毕。现在已经可以调用 Store 的相关方法了,但一般不需要直接使用它们。下面介绍如何使用 react-redux 集成 Store 和 React 组件,简化显式通过上下文传递 Store 的复杂性。


react-redux介绍

在没有使用 react-redux 之前,我们可以通过组件属性传递 store 或者显式地通过组件的上下文对象传递 Store。但当组件树变得复杂时,需要编写的重复代码就会越来越多。又若把 Store 置于全局变量中,那数据的流向就会变得混乱。为此,react-redux 为我们提供了 Provider 组件和 connect 高阶函数解决这一问题。

安装 react-redux:

yarn add react-redux

1、使用 Provider 组件封装 App 组件。Provider 会将 Store 添加到上下文,以便 App 组件树可以访问它。

import React from "react";
import {render} from "react-dom";
import {Provider} from "react-redux";
import App from "./App";
import configureStore from "./store";

const store = configureStore();

render(
    <Provider store={store}>
        <App/>
    </Provider>
    , document.getElementById('react-container')
);

值得注意,Provider 接收单个组件作为参数,并把 Store 作为一个属性传入。


2、使用 react-redux 的 connect 高阶函数创建容器组件,以保持它下面的 UI 组件是无状态的、纯粹的。

import {connect} from "react-redux";
import {ClearUI} from "../components/clear/";
import {clear, resetClear} from "../actionCreator/clear";

export const ClearContainer = connect(
    state => ({
        isFetching: state.clear.isFetching,
        clearSuccess: state.clear.clearSuccess
    }), dispatch => ({
        onClear() {
            dispatch(clear());
        },
        onResetClear() {
            dispatch(resetClear());
        }
    })
)(ClearUI);

connect 高阶函数的内层函数接收两个函数作为参数。第一个函数接收 state 作为参数,返回属性名称映射 state 数据节点值的对象。第二个函数接收 store 的 dispatch 方法作为参数,返回回调函数属性映射分发相关 Action 的函数的对象。就这样,react-redux 帮助我们快速地将 Store 和 UI 组件连接到一起了。这些状态属性和回调函数属性将以 props 的形式传递给相应的子组件。


React 组件的注意问题

在实践中,使用了 react-redux 后,在编写 React 组件时有一些问题需要注意:

  • UI 组件的所有展示状态都应该只取决于接收的属性,遵循纯函数的约束。
  • 一般地,容器组件下的所有子组件都应该是无状态的。在某些特殊情况下,子组件可以维护自身的一些 state。这时候需要谨慎处理组件自身 state 和 props 之间的映射关系。
  • 在使用类组件和第三方库时,要把握好 props 更新触发的生命周期函数和 props 的变化过程,避免不必要的、重复的渲染。
查看类组件各生命周期函数及注意问题,可以参阅React类组件生命周期一览

版权声明:

本文为博主原创文章,若需转载,须注明出处,添加原文链接。

https://leeguangxing.cn/blog_post_20.html