从 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 开发,提升了开发效率和项目的可维护性。

完。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,622评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,716评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,746评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,991评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,706评论 6 413
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,036评论 1 329
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,029评论 3 450
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,203评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,725评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,451评论 3 361
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,677评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,161评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,857评论 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,266评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,606评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,407评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,643评论 2 380

推荐阅读更多精彩内容