zoukankan      html  css  js  c++  java
  • 从零开始的react入门教程(十),快速上手react-redux,相对于redux它究竟简化了什么?

    壹 ❀ 引

    在前面两篇文章中,我们介绍了reduxcontext部分概念与基本用法,这里我们做个简单复习。

    redux属于应用数据流框架,主要用于应用状态的管理,比如react中的state。其数据流为view-->action-->reducer-->store-->view,比如用户点击了一个按钮,本质触发的是store.dispatch(action),然后reducer感知事件,触发actionType对应的更新数据方法,从而达到更新store的目的,而当store更新后,早在view订阅的store.subscribe又被触发,这样又通过store.getState拿到最新的store并将其设置为组件的statestate发生变化自然会让组件重新render,这便是一次完整的数据更新过程。

    而随之问题也暴露出来了,多个组件需要使用store的数据都得引入store.js文件,而且dispatch以及subscribe等API都在store上,所以你需要使用这些方法的地方也一样得提前引入store。于是,我们紧接着介绍了context概念,context(上下文)的作用主要用于解决组件跨级传值问题,比如上面提到的API方法,我们就可以通过context.Providervaluestore传递下去,这样不管哪个组件都可以通过类似this.context.dispatch的写法调用store上的方法。除了全局传递外,比如A-->B-->C-->D场景,D需要拿到A的数据,但是又不希望BC作为数据传递的工具人,使用context.Consumer同样能解决这一类场景的问题。

    接下来要介绍的react-reduxredux作者为react量身定制的库,使用上进一步简化了我们对于redux以及context的写法。由于react-redux是独立redux的存在,因此我们需要单独引入它。在项目根目录执行npm install --save react-redux,接下来我们来了解其在用法的变化,没关系,有前两篇文章的铺垫,这并不会很难!本文开始。

    贰 ❀ react-redux

    react-redux提供了ProviderconnectmapStateToPropsmapDispatchToProps四个API,我们来一一介绍它们。

    贰 ❀ 壹 Provider

    在之前context中我们使用Provider得先通过React.createContext()创建,不过有了react-redux后我们可以直接引用,比如:

    import { Provider } from 'react-redux'
    

    其用法与含义与之前完全相同,我们还是使用之前文章的例子,在index中做部分修改,将Provider的引用改为react-redux

    import React, { Component } from 'react';
    import { Provider } from 'react-redux'
    import ReactDOM from 'react-dom';
    import store from './Store.js';
    import Counter from './Counter.js';
    import Summary from './Summary.js';
    class ControlPanel extends Component {
        render() {
            return (
                <div>
                    <Counter caption="First" />
                    <Counter caption="Second" />
                    <hr />
                    <Summary />
                </div>
            );
        }
    }
    ReactDOM.render(
        <Provider value={store}>
            <ControlPanel />
        </Provider>,
        document.getElementById('root')
    );
    

    注意,这次我们把Provider用在了ReactDOM.render中,也就是进行了真正意义上的全局包裹,之前文章的例子由于ControlPanel组件自身没有使用到store的数据,而是它的子组件需要用,所以之前的写法如下,也没有什么问题:

    class ControlPanel extends Component {
        render() {
            return (
              	//我们使用了Provider包裹子组件,通过value传递store
                <context.Provider value={store}>
                    <div>
                        <Counter caption="First" />
                        <Counter caption="Second" />
                        <hr />
                        <Summary />
                    </div>
                </context.Provider>
            );
        }
    }
    ReactDOM.render(
        <ControlPanel />,
        document.getElementById('root')
    );
    

    这里只是做个写法纠正说明,并不是react-redux特性如此必须这么写,所以单独做个说明。保存后运行项目会报错,毕竟后续还有代码还没改完,我们先这样。

    贰 ❀ 贰 connect

    上面的代码修改,我们为全局上下文中添加了数据store,还记得在之前context介绍中如何使用上下文中的数据吗?两种方式,第一种是通过conponentName.contextType = context先为当前组件绑定上下文,之后在constructor中通过super(context)引入,之后就可以通过this.context访问上下文了,第二种方式通过Consumer中回调函数形参直接访问。

    而在react-redux中我们通过connect方法帮助组件链接全局上下文,比如:

    import { connect } from 'react-redux';
    export default connect(mapStateToProps, mapDispatchToProps)(component);
    

    上面代码中component就是你当前创建的组件,由于这个组件没有与上下文扯上关系,所以这里我们使用connect帮助它链接全局store,其次connect接受两个参数,分别是mapStateToPropsmapDispatchToProps,有什么用我们后面再介绍。

    这里我们补充一个概念,react-redux将组件分为UI组件容器组件UI组件很好理解,只负责view渲染,不管理state变化,不管理业务逻辑,也不使用redux的API,它所接受的一切数据方法均由props提供。

    而所谓容器组件作用与UI组件互补,它的工作是负责state变更以及业务逻辑处理,而这里的容器组件其实由react-redux生成。

    注意上面connect这行代码,它本质上等同于:

    // 帮UI组件注入数据方法,于是得到了一个新的容器组件,connect就像给一部手机通了电一样,让其有了生命力
    const 容器组件 = connect(mapStateToProps, mapDispatchToProps)(UI组件);
    export 容器组件;
    

    也就是说我们通过connect帮助一个UI组件链接到了数据层,这里的数据可以是全局的store,也可以是上层组件传递下来的props。于是我们得到了一个被注入了数据的新组件。

    说到这里,不知道大家能不能感受到这种做法与context使用全局store的差异性,context使用数据要么直接把上下文与组件绑定后使用,要么借用Consumer使用,就像被内嵌进组件一样,耦合度较高。而connect更像在组件外部给其开了一个传递数据的入口,不管你数据哪里来的,都通过我这里传递进入,方式上就比较统一了,无论是全局store还是上次的props,都可以通过这里以props的方式统一注入,那么组件内部呢都给我通过props的方式访问外部传递进来的方法或者数据。

    当然,上述关于UI组件的分类说明比较严格了,实际项目开发中也存在很多包含了业务代码以及自身state的组件使用connect链接store的做法。这里只是科普UI组件容器组件的概念,有时候硬要将一个组件抽离成两个组件反而是一件麻烦事,具体看大家习惯。

    上面说了connect用于帮助组件链接数据,那么具体做呢?其实就得依赖上面提到的两个参数了,我们接着说。

    贰 ❀ 叁 mapStateToProps

    顾名思义,建立stateprops的映射,mapStateToProps接受2个参数,比如:

    const mapStateToProps = (state,ownProps) => {
      return {
        name:state.name,
        age:ownProps.age
      }
    }
    

    说直白点,比如从全局的storestore本身可以理解为全局的state)以及外层组件传递给你的props中提取并组合成当前组件所需要的数据。

    上面的代码中返回了一个新的对象,这个对象包含了一个name一个age,数据来源分别是全局state与外层组件传递的props,那么当前组件通过this.props即可访问到这两个属性,而且,无论是外部的state还是外部传递的ownProps发生了变化,都会再次触发此方法(如果你没用这两个参数就不会触发,毕竟没有依赖关系),目的是同步更新传递当前组件的props

    贰 ❀ 肆 mapDispatchToProps

    看到方法名中的dispatch,直觉应该想到此方法应该跟store.dispatch有关。没错,我们在学习context时,凡是组件需要派发action时最终调用的都是this.context.dispatch,写法上多少有些繁琐。mapDispatchToProps的作用,其实也是将dispatch行为抽离了出去,然后也作为props一部分传进了组件,此方法接受两个参数,如下:

    function mapDispatchToProps(dispatch, ownProps) {
      return {
        onIncrement: () => {
          dispatch(Actions.increment(ownProps.caption));
        },
      }
    }
    

    dispatch作用其实与store.dispatch作用相同,只是react-redux对齐进行了封装,我们不用再借用this.context.dispatch调用,而是可以直接在mapDispatchToProps方法中使用,是不是用法上便捷了很多呢?其次,mapDispatchToProps返回了一个对象,包含了一个方法,那么在当前组件内部,调用this.props.onIncrement本质上其实就是在做派发。

    ownPropsmapStateToProps的第二个参数作用相同,都是外层组件组件传递的props

    叁 ❀ 一个例子

    OK,我们介绍了react-redux带来的新概念,让我们使用这些API改写上一篇文章中的例子,近距离感受它们所带来的便捷性。在前面我们其实已经完成了对index.js的改写,接下来要做的是对Counter组件与Summary组件的改写,先上Counter.js的代码:

    import React, { Component } from 'react';
    import PropTypes from 'prop-types';
    import * as Actions from './Actions.js';
    import { connect } from 'react-redux'
    class Counter extends Component {
      render() {
        const { onIncrement, onDecrement, caption, value } = this.props;
        return (
          <div>
            <button onClick={onIncrement}>+</button>
            <button onClick={onDecrement}>-</button>
            <span>{caption} count: {value}</span>
          </div>
        );
      }
    }
    
    Counter.propTypes = {
      caption: PropTypes.string.isRequired,
      onIncrement: PropTypes.func.isRequired,
      onDecrement: PropTypes.func.isRequired,
      value: PropTypes.number.isRequired
    };
    // 用于将`store`的数据加工成当前组件所需要的数据
    function mapStateToProps(state, ownProps) {
      console.log(state);
      console.log(ownProps);
      return {
        value: state[ownProps.caption]
      }
    }
    // dispatch的操作都被提到这里了,组件瞬间就干净了
    function mapDispatchToProps(dispatch, ownProps) {
      return {
        onIncrement: () => {
          dispatch(Actions.increment(ownProps.caption));
        },
        onDecrement: () => {
          dispatch(Actions.decrement(ownProps.caption));
        }
      }
    }
    
    export default connect(mapStateToProps, mapDispatchToProps)(Counter);
    

    这里我们尝试打印mapStateToProps两个参数,你会发现其实state就是全局storeownProps就是父级组件传下来的props

    你会发现,Counter组件活生生被抽离成了一个UI组件,以前我们还在组件中定义包裹dispatch的方法,定义初始化state的方法,还需要添加subscribe订阅store的变化,但现在,store的变化感知以及组件state的初始化都交给了mapStateToProps来完成,前面说了,只要外层state变化,都会触发此方法重新渲染。

    同理,对于dispatch方法的定义我们也交给了mapDispatchToProps来完成,而组件内部只需要通过this.props.onIncrement就能执行action派发,是不是相对于之前的写法,简洁了很多呢?

    同理,我们修改Summary组件,代码如下:

    import React, { Component } from 'react';
    import PropTypes from 'prop-types';
    import { connect } from 'react-redux'
    class Summary extends Component {
      render() {
        const {sum} = this.props;
        return (
          <div>Total Count: {sum}</div>
        );
      }
    }
    
    Summary.propTypes = {
      sum: PropTypes.number.isRequired
    };
    
    function mapStateToProps(state) {
      let sum = 0;
      for (const key in state) {
        if (state.hasOwnProperty(key)) {
          sum += state[key];
        }
      }
      return {sum};
    }
    
    export default connect(mapStateToProps)(Summary);
    

    保存代码,这个小例子又运行起来,相较于redux结合context的写法,react-redux确实很大程度上精简了代码量。

    肆 ❀ 总

    那么到这里,我们通过react-redux再次改写了之前的例子,通过这篇文章,我们知道react-redux与传统redux的差异性,从写代码的角度,不得不说react-redux更加的精简与方便。当然,redux其实还有不少进阶知识我们还未提及,比如中间件,比如异步处理,再或者对于reducer的合并等等,这些知识在后面的文章我们会慢慢介绍,那么本文到此结束。

    参考

    Redux 中文文档

    Redux 入门教程(三):React-Redux 的用法

    深入浅出React和Redux第三章

  • 相关阅读:
    浅谈C++ STL中的优先队列(priority_queue)
    哈夫曼树与哈夫曼编码
    Binary Search 的递归与迭代实现及STL中的搜索相关内容
    Prime 算法的简述
    估算网站需要多少宽带方法
    微服务服务拆分步骤
    第一次有人把科创板讲得这么简单明了
    一个人优秀到可怕的三个迹象!一旦具备,必为人中龙凤,大富大贵
    美元加息怎么“剪羊毛”
    英国脱欧的影响
  • 原文地址:https://www.cnblogs.com/echolun/p/14970437.html
Copyright © 2011-2022 走看看