前言
您将在本文当中学习到
- react-redux是什么,解决什么问题
- UI组件以及容器组件
- react-redux库两个重要的API,Provider以及connect
-
mapStateToProps以及mapDispatchToProps等的学习
React进阶(6)-react-redux的使用.png
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组件(傻瓜组件/无状态组件)
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库中引入Provider
import 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 路由库时
,与其他项目没有不同之处,也是使用Provider
在Router
外面包一层,因为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等,很多东西很抽象
学习起来,就有些费劲~
最后,看完本节:记住几点
- Provider是一个由react-redux提供的组件,用于接收store的数据,供内部组件暴露的一个接口
- connect是react-redux库提供的一个函数,用于连接UI组件的,并且最终生成一个容器组件,提供了一些映射方法,mapStateToProps以及mapDispatchToProps
-
在UI组件内部的数据通过this.props来填充渲染
itclancoder二维码.jpg
网友评论