React进阶(6)-react-redux的使用

时间:2022-07-28
本文章向大家介绍React进阶(6)-react-redux的使用,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前言

您将在本文当中学习到

  • react-redux是什么,解决什么问题
  • UI组件以及容器组件
  • react-redux中两个重要的API,Provider以及connect
  • mapStateToProps以及mapDispatchToProps等的学习

是不是搞不清楚React与Redux,以及React-Redux的关系?是不是不清楚mapStateToProps以及mapDispatchToProps的使用?

那么本文就是你想要知道的

react-redux是什么?

以下是上节内容的代码结构,完成的一个todolist,并对Redux进行了拆分,按功能模块化管理

├─.gitignore├─package-lock.json├─package.json├─README.md├─yarn-error.log├─yarn.lock├─src                              // 源代码主要目录|  ├─index.js                   // 入口文件|  ├─views                      // 视图|  |   └TodoList.js            |  ├─store                    // Redux中store组件的公共数据状态|  |   ├─actionCreators.js   // action创建者|  |   ├─actionTypes.js       // actionTypes的类型,定义成常量|  |   ├─index.js                 // 创建store的主文件|  |   └reducer.js                // 创建的reducer|  ├─components             // UI组件|  |     └TodoListUI.js├─public|   ├─favicon.ico|   ├─index.html|   └manifest.json

Redux:是一个用于管理组件公共状态的一个可预测状态的框架,集中管理组件的状态.核心在于store,它提供了dispatch,getState,subscribe方法,理解Redux的工作流程很重要

react-redux: 它是redux作者封装的一个库,是一个第三方的模块,对Redux进一步的封装简化,提供了一些额外的API(例如:Provider,connect等),使用它可以更好的组织和管理我们的代码,遵循一定的组件拆分规范,在React中更方便的使用Redux

关系: 它不是必须的,在实际项目中,可选用.是使用Redux还是使用react-redux,取决于你自己,项目组成员的熟悉程度,适合自己的才是最好的,使用后者提供了一些便利,但需要额外的掌握一些API的使用

如果只是使用Redux,那么流程是这样的:

  • component-->dispatch(action)-->reducer-->subscribe-->getState-->component 这在前几篇的内容,一直都是遵循这个流程

如果使用react-redux,那么流程是这样的:

  • component-->actionCreator(data)-->reducer-->component

在上几节内容中,我们将todolist的组件进行了拆分,拆分成UI组件(无状态组件)和容器组件,将Reudcer按照各个职责进行管理

虽然已经做了简化,但是想更进一步更好的组织我们的代码,那么可以使用react-redux,当你使用了它之后,你不需要手动的写dispatch,subscribe,以及getState了

因为它对内输入的逻辑(即外部的数据(即state对象)如何转换为 UI 组件的参数,通过mapStateToProps),对外输出逻辑(即用户发出的动作如何变为 Action 对象,从 UI 组件传出去,通过mapDispatchToProps)

react-redux帮我们做了监听,获取state等工作,同时它提供了两个好用的API,Provider和connect,在下文中我们会学习到的

安装react-redux

既然是一个第三方的模块,那么可以通过npm或者yarn的方式下载

npm install --save react-redux或yarn add react-redux

安装完成后,可以在根目录的package.json中查看是否有的

对于理解 react-redux中的 Provider和 connect,有必要再次回顾一下之前学过的UI组件和容器组件

UI组件(傻瓜组件/无状态组件)

既然是一个第三方的模块,那么可以通过npm或者yarn的方式下载

react-redux将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)

UI 组件有以下几个特征

  • 只负责 UI 的呈现,不带有任何业务逻辑,
  • 没有状态,UI的渲染通过外部的props传入(即不使用this.state这个变量)
  • 所有数据都由参数(this.props)对象提供
  • 不使用任何 Redux 的 API

如下所示, UI 组件的例子

const Counter = num => <h1>{ num }</h1>;

因为不含有状态state,UI 组件又称为"纯组件",即它纯函数一样,输出的结果纯粹由参数决定它的值,给定的输入,便会有指定的输出,与UI = render(data)完全吻合

容器组件(聪明组件)

容器组件的特征与UI组件相反

  • 负责管理数据和业务逻辑,不负责 UI 的呈现
  • 带有内部状态(state)
  • 使用 Redux 的 API(下面会有具体的例子),比如:dispatch,getState,subscribe等

总之:UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。

如果一个组件既有 UI 又有业务逻辑,那怎么办?可以将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。

这也是之前我们将todolist组件进行了容器组件和UI组件不断的拆分的方式.当然这种拆分因人而异,没有绝对的,太细粒度的拆分也会带来管理上的麻烦.不能为了拆分而拆分.

而 react-redux规定,所有的 UI 组件都由用户提供,容器组件则是由 react-redux自动生成(下面的connect方法返回的结果就是容器组件)。也就是说,用户负责视觉层,状态管理则是全部交给它

Provider

作用:它是一个组件,用于连接了Store,它把store提供给内部组件,接受store作为props,然后通过context往下传,这样react中任何组件都可以通过context获取store

只要被这个 Provider组件包裹了,那么它内部的子组件就有能力接收到store,内部的组件都有能力获取store的数据的

这样也就意味着我们可以在任何一个组件里利用 dispatch(action)来触发 reducer改变 state,并用 subscribe监听 state的变化,然后通过 getState来获取变化后的值。但是官方并不推荐这样做,它只会让数据流变的混乱,过度的耦合也会影响组件的复用,维护起来会更麻烦

Provider其实是对Redux中的store的subscribe,dispatch,getState的一个封装,集成.它对外暴露props属性,内部却已经帮我们实现了的 react-redux提供 Provider组件,可以让容器组件拿到state 例如如下代码:

import React from 'react';import ReactDOM from 'react-dom';import TodoList from './TodoList';import { Provider } from "react-redux";  // 从react-redux库中引入Providerimport store from './store'                      // 引入store


const container = document.getElementById('root');
ReactDOM.render(    <Provider store={store}>     // 通过属性props的方式将store赋值给store,这样Provider组件就能接收到store中的数据,其内部的组件也可以拿到store中的状态        <TodoList />    </Provider>,    container);

如果为了代码更好看点,也可以这样,定义一个变量的,以下这种写法与上面是等价的,JSX的内容可以看以前的内容

const App = (    <Provider store={store}>        <TodoList />    </Provider>)
const container = document.getElementById('root');ReactDOM.render(App,container);

这里需要注意的是:当你使用React-Router 路由库时,与其他项目没有不同之处,也是使用ProviderRouter外面包一层,因为Provider的唯一功能就是传入store对象 如果不这样包裹着:内部的组件时接收不到store中的状态数据的,如下所示

 <Provider store={store}>    <Router>      <Route path="/" component={App} />    </Router>  </Provider>

connect

作用:connect顾名思义,是一个连接器,它是连接容器组件和UI(傻瓜)组件的,它是 react-redux提供的一个方法,用于从 UI 组件生成容器组件,把两种组件给连接起来

connect方法接收四个参数,一个是 mapStateToProps,另一个是 mapDispatchToProps,当然还有两个参数: mergeProps, options,它们是可选的,它执行的结果依然是一个函数,所以才可以在后面在加上一个圆括号的,而圆括号内又接收一个参数,即是UI组件,也是傻瓜组件

有两次 connect的执行,第一次 connect函数的执行是从react-redux库中引入这个方法,第二次是把 connect函数返回的函数再次执行,最后产生的就是容器组件,如下代码所示

import { connect } from 'react-redux'const VisibleTodoList = connect()(TodoList);  // 命名成ContainerTodoList也是一样的

如果不给connect传递任何参数,可以为空,也可以指定参数null,或者只有mapStateToProps,没有mapDispatchToProps,这也是没有什么问题的,如下代码所示

import { connect } from 'react-redux'const VisibleTodoList = connect(mapStateToProps,null)(TodoList);

在上面代码中,TodoList就是 UI 组件,而VisibleTodoList就是由 React-Redux通过connect方法自动生成的容器组件。

但是,因为没有定义业务逻辑,上面这个容器组件毫无意义,只是 UI 组件的一个单纯的包装层。为了定义业务逻辑,需要给出下面两方面的信息。

  1. 输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数(负责接收state)
  2. 输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去(负责派发动作dispatch方法)

所以,connect的两个API如下所示:

import { connect } from 'react-redux'const VisibleTodoList = connect(mapStateToProps, mapDispatchToProps,mergeProps, options)(TodoList);  // 命名成ContainerTodoList也是一样的

在上面代码中,connect方法接受四个参数:分别是 mapStateToProps和 mapDispatchToProps,后面两个参数 mergeProps,以及 options可以省略,这四个参数的名字可以是任意的,并不一定非得这样叫,也可以定义为 mapState或者 mapDispatch,只是这样命名,见名知意,已经是约定俗成的一个习惯

它们定义了 UI 组件的业务逻辑。前者负责输入逻辑(mapStateToProps),即将state映射到 UI 组件的参数(props),后者负责输出逻辑(mapDispatchToProps),即将用户对 UI 组件的操作映射成 Action

综归来说, connect做了两件事情:

  • 把store上的状态转换为内层的UI组件(傻瓜组件)的props
  • 把内层UI组件(无状态组件)中的用户触发的动作转化为派送个store的动作,前者(mapStateToProps)是一个内层傻瓜组件对象的输入,后者(mapDispatchToProps)内层傻瓜组件的输出

mapStateToProps与mapDispatchToProps的工作套路就是:把Store上的状态转化为内层组件的props,然后在组件内部通过 this.props的方式拿到,这是不同于之前 this.state的方式的,其实就是一个映射关系。

mapStateToProps(state, [ownProps])

mapStateToProps是一个函数。见名思义,它是建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。

既然作为函数, mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射

mapStateToProps 接受两个参数,第一个是 state,第二个是 ownProps, store的 state和自定义的 props,并返回一个新的对象,这个对象会作为 props的一部分传入 ui组件。

我们可以根据组件所需要的数据自定义返回一个对象。ownProps的变化也会触发 mapStateToProps, ownProps代表容器组件的 props对象

const mapStateToProps = (state) => {  // state代表的是store中state的状态    return {        inputValue: state.inputValue,        list: state.list    }}

在上面代码中, mapStateToProps是一个函数,它接受state作为参数,并且第一个参数就是 state, 它返回一个对象。这个对象有 inputValue和 list属性,它代表着 UI 组件的同名参数,后面的 state.inputValue,以及 state.list就是从 Store中的 state的拿到内部组件输入框的值和底下列表的值

mapStateToProps会订阅 Store,每当 state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。

mapDispatchToProps(dispatch,[ownProps])

mapDispatchToProps是 connect函数的第二个参数,它是用来建立 UI 组件的参数到 store.dispatch方法的映射。 

换句话说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。

如果 mapDispatchToProps是一个函数,会得到 dispatch和 ownProps(容器组件的props对象)两个参数

const mapDispatchToProps = (dispatch, ownProps) => {    return {       handleInputChange(e) {            const action = {                type: "handle_input_change",                value: e.target.value            }            dispatch(action);       },
       handleAddContent() {        const action = {            type: "handle_add_content"        }        dispatch(action);       },
       handleDeleteList(index) {        const action = {            type: 'handle_delete_list',            index        }        dispatch(action);       }    }  }

从上面代码可以看到, mapDispatchToProps作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action。

如果 mapDispatchToProps是一个对象,那么会和 store绑定作为 props的一部分传入ui组件,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator,返回的 Action会由 Redux 自动发出。举例来说,上面的 mapDispatchToProps写成对象,则如下所示:下面的函数是Es6的简写形式

const mapDispatchTopProps = {     handleInputChange(e) {   // 等价于handleInputChange: function(e){ ...}            const action = {                type: "handle_input_change",                value: e.target.value            }            dispatch(action);       },
       handleAddContent() {        const action = {            type: "handle_add_content"        }        dispatch(action);       },
       handleDeleteList(index) {        const action = {            type: 'handle_delete_list',            index        }        dispatch(action);       } }

不论 mapDispatchToProps是对象还是函数,它最终都会返回一个对象,如果是函数,这个对象的 key值是可以自定义的

function mapDispatchToProps(dispatch) {   return {      attrActions: bindActionCreators(todoActionCreators, dispatch) // bindActionCreators是Redux的一个方法,会将action和dispatch绑定并返回一个对象,这个对象会和ownProps一起作为props的一部分传入ui组件   };}

mapDispatchToProps返回的对象其属性其实就是一个个 actionCreator,因为已经和 dispatch绑定,所以当调用 actionCreator时会立即发送action,而不用手动dispatch

mapStateToProps和 mapDispatchToProps都可以包含第二个参数 ownProps,ownProps的变化也会触发 mapDispatchToProps

mergeProps(stateProps, dispatchProps, ownProps)

作用:它是 connect函数的第三个参数,将 mapStateToProps()与 mapDispatchToProps()返回的对象和组件自身的 props合并成新的 props并传入组件。默认返回 Object.assign({}, ownProps, stateProps, dispatchProps)的结果

const mergeProps = () => {      return Object.assign({}, ownProps, stateProps, dispatchProps)}

options

当 pure = true表示 connect容器组件将在 shouldComponentUpdate中对 store的 state和ownProps进行浅对比,判断是否发生变化,优化性能。若为false则不对比

这个options有很多,具体可以参考 react-redux官方文档

{  context?: Object,  pure?: boolean,  areStatesEqual?: Function,  areOwnPropsEqual?: Function,  areStatePropsEqual?: Function,  areMergedPropsEqual?: Function,  forwardRef?: boolean,}

结语

本文主要学习了如何使用 react-redux,使用 react-redux只是为了简化Redux的,不使用react-redux也没有问题,只是使用react-redux可以更简便的管理我们的状态,更好的组织我们的代码

但是随之而来的就是学习成本,得学习那些 Provider, connect等API的使用,这也是为什么这些框架令人蛋疼的原因,本以为学了React能搞事,但发现依旧还有一座山在等着你

什么解决异步问题react-thunk,react-saga等中间件,middleWare,路由react-router等

当然最新版本的React中已经有了React hooks,有了这个你可以替代Redux,或者react-redux,但是笔者认为,技术没有金弹或者银弹,适合业务的技术才是最好的

理解redux以及react-redux不妨也是一种技术选择,更好的与React hooks做比较,从而选择一个最适宜的.

React很多东西很抽象 学习起来,就有些费劲~以后也会循序渐进,一一进行分享的

最后,看完本节:记住几点

  • Provider是一个由react-redux提供的组件,用于接收store的数据,供内部组件暴露的一个接口
  • connect是react-redux库提供的一个函数,用于连接UI组件的,并且最终生成一个容器组件,提供了一些映射方法,mapStateToProps以及mapDispatchToProps
  • 在UI组件内部的数据通过this.props来填充渲染