从 Redux 到 React-Redux

上篇文章中,我们用 React 结合 Redux 实现了一个小计算器,并将组件拆分为容器组件和木偶组件,本文再基于这个例子继续做一些拓展,并最终引出 React-Redux。

计算器应用的问题

对于计算器小应用,我们已经实现了其所有的功能,因此不再有功能上的问题了,那么还有没有可以优化的地方呢?当然。
在这个例子中,每个容器组件都会引入 store,这样会产生两个问题:

  • 每个容器组件都去引入 store,有点麻烦
  • 如果我们要写一个插件提供给别人使用,但是并不知道别人的 store 的引用路径,这就有点难办了

或许你会想:如果有个全局变量就好了,将 store 存放在全局变量中,就不用每次都引入了,也不用关心 store 的引用路径了。
那么可不可以提供一个最外层的组件,让其提供 store 对象,然后子组件直接去取用这个对象呢?当然可以。这里就需要用到我们前面说过的 context,可以在这篇文章中查看相应的内容。

context 回顾

context 是组件树上的一个全局属性,这里先回顾一下 context 的使用:

  • 父级需要声明自己支持 context,并提供一个 getChildContext 函数返回初始 context
  • 子组件需要声明自己需要使用 context
  • 子组件取用 context 中的属性时,属性验证(PropTypes)必须和父组件中的声明保持一致
  • 若子组件拥有构造函数(constructor),需要将 context 传入该构造函数,并在 super 调用父类构造函数时一并传入
  • 可以使用 super(...args) 来代替 super(props,context)

定义 Provider 组件

下面,我们来定义一个 Provider 组件,用来在 context 中存放 store 对象,新建一个 Provider.js 文件:

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

export default class Provider extends Component{
    // 声明 context 属性的类型
    static childContextTypes = {
        store:PropTypes.object.isRequired,
    }

    // 返回初始的 context
    getChildContext(){
        return{
            // store 对象由 Provider 组建的 props 传入
            store:this.props.store,
        }
    }

    render(){
        // 直接渲染子节点
        return this.props.children;
    }
}

修改 App.js,让 Provider 作为组件树的根节点,并向其传入 store:

import React,{ Component } from "react";
import Counter from "./ContainerCounter";
import Provider from "./Provider";
import store from "./Store/store";

export default class App extends Component{
    render(){
        return(
            <Provider store = { store }>
                <Counter />
            </Provider>
        );
    }
}

修改 ContainerCounter.js,从 context 中获取 store:

import React,{ Component } from "react";
import PropTypes from "prop-types";
import ACTIONS from "./Store/actions";
import UICounter from "./UICounter";

export default class ContainerCounter extends Component{
    // 声明自己需要取用 context 属性
    static contextTypes = {
        store:PropTypes.object.isRequired,
    }

    constructor(props,context) {
        super(props,context);
        // 获取初始状态
        this.state = {
            // 使用 this.context.store 代替原先的 store
            value:this.context.store.getState().value,
        };
    }

    componentWillMount() {
        // 监听 store 变化
        this.context.store.subscribe(this.watchStore.bind(this));
    }

    componentWillUnmount() {
        // 对 store 变化取消监听
        this.context.store.unsubscribe(this.watchStore.bind(this));
    }

    // 监听回调函数,当 store 变化后执行
    watchStore(){
        // 回调函数中重新设置状态
        this.setState(this.getCurrentState());
    }

    // 从 store 中获取状态
    getCurrentState(){
        return{
            value:this.context.store.getState().value,
        }
    }

    // 增加函数
    increase(){
        // 派发 INCREMENT Action
        this.context.store.dispatch(ACTIONS.increament());
    }

    // 减少函数
    decrease(){
        // 派发 DECREAMENT Action
        this.context.store.dispatch(ACTIONS.decreament());
    }

    render(){
        return(
            <UICounter
                increase = { this.increase.bind(this)}
                decrease = { this.decrease.bind(this)}
                value = { this.state.value }
            />
        );
    }
}

效果和原先保持一致:

利用 context 获取 store.gif

使用 React-Redux

通过前面的例子,我们对应用进行了如下改进:

  • 将组件拆分为容器组件和木偶组件
  • 使用 Provider 顶层组件,通过 context 提供 store

前面说到,Redux 作为一款状态管理工具,可以和任意的框架结合使用,当然,还有一个更加适用于 React 开发的 React-Redux 库。其所作的工作和我们前面的差不多,但增加了不少的易用性。使用 React-Redux 前需要先进行安装:

npm install --save react-redux

下面说一下 React-Redux 库中的两个概念:

  • connect:用来连接木偶组件,并转换为容器组件,是的,我们不需要自己写容器组件了
  • Provider 组件:顶层组件,通过 context 提供 store

connect 函数接受两个参数,这两个参数都是函数:

  • mapStateToProps:从 store 中获取值,并传递给木偶组件
  • mapDispatchToProps:定义回调函数,并将这些回调函数传递给木偶组件,木偶组件调用通过这些函数向 store 派发 action

下面看一下代码实现:
App.js:

import React,{ Component } from "react";
import Counter from "./ContainerCounter";
// 从 react-redux 中引入 Provider 组件
import { Provider } from "react-redux";
import store from "./Store/store";

export default class App extends Component{
    render(){
        return(
            <Provider store = { store }>
                <Counter />
            </Provider>
        );
    }
}

ContainerCounter.js:

import { connect } from "react-redux";
import ACTIONS from "./Store/actions";
import UICounter from "./UICounter";

function mapStateToProps(state){
    return{
        value:state.value
    }
}

function mapDispatchToProps(dispatch){
    return{
        // 增加
        increase(){
            // 派发 action
            dispatch(ACTIONS.increament());
        },
        // 减少
        decrease(){
            dispatch(ACTIONS.decreament());
        }
    }
}

const ContainerCounter = connect(
    mapStateToProps,
    mapDispatchToProps,
)(UICounter);

export default ContainerCounter;

最终效果和前面一样。
可见,使用 react-redux 后,逻辑变得更加清晰了,也减少了许多操作,比如对 store 进行监听(subscribe),声明子组件的 context 等。这些事情 react-redux 都帮我们做了,我们只需定义两个函数(mapStateToProps,mapDispatchToProps)传递给 connect 函数,connect 函数调用的结果也是一个函数,我们再将木偶组件传入这个结果函数,就自动生成了容器组件。更易维护,代码量更少。
除此之外,react-redux 还进行了一些优化的处理,比如 shouldComponentUpdate 的优化等,但核心原理没有变,只是进行了一层封装处理。
因此,在使用 react-redux 之前,最好对 Redux 有一些了解,否则会因为不清楚内部的原理而成为代码搬运工。

总结

本文首先从全局 store 入手,构造了 Redux 版本的 Provider 组件,在此基础上引入了更加方便的 react-redux 库,更加适用于 React 开发,提升了开发效率和项目的可维护性。

完。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容