动手实现 React-redux(三):connect 和 mapStateToProps
我们来观察一下刚写下的这几个组件,可以轻易地发现它们有两个重大的问题:
- 有大量重复的逻辑:它们基本的逻辑都是,取出 context,取出里面的 store,然后用里面的状态设置自己的状态,这些代码逻辑其实都是相同的。
- 对 context 依赖性过强:这些组件都要依赖 context 来取数据,使得这个组件复用性基本为零。想一下,如果别人需要用到里面的
ThemeSwitch
组件,但是他们的组件树并没有 context 也没有 store,他们没法用这个组件了。
对于第一个问题,我们在 高阶组件 的章节说过,可以把一些可复用的逻辑放在高阶组件当中,高阶组件包装的新组件和原来组件之间通过 props
传递信息,减少代码的重复程度。
对于第二个问题,我们得弄清楚一件事情,到底什么样的组件才叫复用性强的组件。如果一个组件对外界的依赖过于强,那么这个组件的移植性会很差,就像这些严重依赖 context 的组件一样。
如果一个组件的渲染只依赖于外界传进去的 props
和自己的 state
,而并不依赖于其他的外界的任何数据,也就是说像纯函数一样,给它什么,它就吐出(渲染)什么出来。这种组件的复用性是最强的,别人使用的时候根本不用担心任何事情,只要看看 PropTypes
它能接受什么参数,然后把参数传进去控制它就行了。
我们把这种组件叫做 Pure Component,因为它就像纯函数一样,可预测性非常强,对参数(props
)以外的数据零依赖,也不产生副作用。这种组件也叫 Dumb Component,因为它们呆呆的,让它干啥就干啥。写组件的时候尽量写 Dumb Component 会提高我们的组件的可复用性。
到这里思路慢慢地变得清晰了,我们需要高阶组件帮助我们从 context 取数据,我们也需要写 Dumb 组件帮助我们提高组件的复用性。所以我们尽量多地写 Dumb 组件,然后用高阶组件把它们包装一层,高阶组件和 context 打交道,把里面数据取出来通过 props
传给 Dumb 组件。
我们把这个高阶组件起名字叫 connect
,因为它把 Dumb 组件和 context 连接(connect)起来了:
import React, { Component } from 'react'
import PropTypes from 'prop-types' export connect = (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
} // TODO: 如何从 store 取数据? render () {
return <WrappedComponent />
}
} return Connect
}
connect
函数接受一个组件 WrappedComponent
作为参数,把这个组件包含在一个新的组件 Connect
里面,Connect
会去 context 里面取出 store。现在要把 store 里面的数据取出来通过 props
传给 WrappedComponent
。
但是每个传进去的组件需要 store 里面的数据都不一样的,所以除了给高阶组件传入 Dumb 组件以外,还需要告诉高级组件我们需要什么数据,高阶组件才能正确地去取数据。为了解决这个问题,我们可以给高阶组件传入类似下面这样的函数:
const mapStateToProps = (state) => {
return {
themeColor: state.themeColor,
themeName: state.themeName,
fullName: `${state.firstName} ${state.lastName}`
...
}
}
这个函数会接受 store.getState()
的结果作为参数,然后返回一个对象,这个对象是根据 state
生成的。mapStateTopProps
相当于告知了 Connect
应该如何去 store 里面取数据,然后可以把这个函数的返回结果传给被包装的组件:
import React, { Component } from 'react'
import PropTypes from 'prop-types' export const connect = (mapStateToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
} render () {
const { store } = this.context
let stateProps = mapStateToProps(store.getState())
// {...stateProps} 意思是把这个对象里面的属性全部通过 `props` 方式传递进去
return <WrappedComponent {...stateProps} />
}
} return Connect
}
connect
现在是接受一个参数 mapStateToProps
,然后返回一个函数,这个返回的函数才是高阶组件。它会接受一个组件作为参数,然后用 Connect
把组件包装以后再返回。 connect
的用法是:
...
const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
}
}
Header = connect(mapStateToProps)(Header)
...
有些朋友可能会问为什么不直接
const connect = (mapStateToProps, WrappedComponent)
,而是要额外返回一个函数。这是因为 React-redux 就是这么设计的,而个人观点认为这是一个 React-redux 设计上的缺陷,这里有机会会在关于函数编程的章节再给大家科普,这里暂时不深究了。
我们把上面 connect
的函数代码单独分离到一个模块当中,在 src/
目录下新建一个 react-redux.js
,把上面的 connect
函数的代码复制进去,然后就可以在 src/Header.js
里面使用了:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from './react-redux' class Header extends Component {
static propTypes = {
themeColor: PropTypes.string
} render () {
return (
<h1 style={{ color: this.props.themeColor }}>React.js 小书</h1>
)
}
} const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
}
}
Header = connect(mapStateToProps)(Header) export default Header
可以看到 Header
删掉了大部分关于 context 的代码,它除了 props
什么也不依赖,它是一个 Pure Component,然后通过 connect
取得数据。我们不需要知道 connect
是怎么和 context 打交道的,只要传一个 mapStateToProps
告诉它应该怎么取数据就可以了。同样的方式修改 src/Content.js
:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ThemeSwitch from './ThemeSwitch'
import { connect } from './react-redux' class Content extends Component {
static propTypes = {
themeColor: PropTypes.string
} render () {
return (
<div>
<p style={{ color: this.props.themeColor }}>React.js 小书内容</p>
<ThemeSwitch />
</div>
)
}
} const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
}
}
Content = connect(mapStateToProps)(Content) export default Content
connect
还没有监听数据变化然后重新渲染,所以现在点击按钮只有按钮会变颜色。我们给 connect
的高阶组件增加监听数据变化重新渲染的逻辑,稍微重构一下 connect
:
export const connect = (mapStateToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
} constructor () {
super()
this.state = { allProps: {} }
} componentWillMount () {
const { store } = this.context
this._updateProps()
store.subscribe(() => this._updateProps())
} _updateProps () {
const { store } = this.context
let stateProps = mapStateToProps(store.getState(), this.props) // 额外传入 props,让获取数据更加灵活方便
this.setState({
allProps: { // 整合普通的 props 和从 state 生成的 props
...stateProps,
...this.props
}
})
} render () {
return <WrappedComponent {...this.state.allProps} />
}
} return Connect
}
我们在 Connect
组件的 constructor
里面初始化了 state.allProps
,它是一个对象,用来保存需要传给被包装组件的所有的参数。生命周期 componentWillMount
会调用调用 _updateProps
进行初始化,然后通过 store.subscribe
监听数据变化重新调用 _updateProps
。
为了让 connect 返回新组件和被包装的组件使用参数保持一致,我们会把所有传给 Connect
的 props
原封不动地传给 WrappedComponent
。所以在 _updateProps
里面会把 stateProps
和 this.props
合并到 this.state.allProps
里面,再通过 render
方法把所有参数都传给 WrappedComponent
。
mapStateToProps
也发生点变化,它现在可以接受两个参数了,我们会把传给 Connect
组件的 props
参数也传给它,那么它生成的对象配置性就更强了,我们可以根据 store
里面的 state
和外界传入的 props
生成我们想传给被包装组件的参数。
现在已经很不错了,Header.js
和 Content.js
的代码都大大减少了,并且这两个组件 connect 之前都是 Dumb 组件。接下来会继续重构 ThemeSwitch
。
下一节:动手实现 React-redux(四):mapDispatchToProps
上一节:动手实现 React-redux(二):结合 context 和 store
动手实现 React-redux(三):connect 和 mapStateToProps的更多相关文章
- react+redux教程(一)connect、applyMiddleware、thunk、webpackHotMiddleware
今天,我们通过解读官方示例代码(counter)的方式来学习react+redux. 例子 这个例子是官方的例子,计数器程序.前两个按钮是加减,第三个是如果当前数字是奇数则加一,第四个按钮是异步加一( ...
- [Redux] Generating Containers with connect() from React Redux (VisibleTodoList)
Learn how to use the that comes with React Redux instead of the hand-rolled implementation from the ...
- react+redux教程(三)reduce()、filter()、map()、some()、every()、...展开属性
reduce().filter().map().some().every()....展开属性 这些概念属于es5.es6中的语法,跟react+redux并没有什么联系,我们直接在https:// ...
- webpack+react+redux+es6开发模式
一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入 ...
- angular开发者吐槽react+redux的复杂:“一个demo证明你的开发效率低下”
曾经看到一篇文章,写的是jquery开发者吐槽angular的复杂.作为一个angular开发者,我来吐槽一下react+redux的复杂. 例子 为了让大家看得舒服,我用最简单的一个demo来展示r ...
- webpack+react+redux+es6
一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入 ...
- react+redux渲染性能优化原理
大家都知道,react的一个痛点就是非父子关系的组件之间的通信,其官方文档对此也并不避讳: For communication between two components that don't ha ...
- 详解react/redux的服务端渲染:页面性能与SEO
亟待解决的疑问 为什么服务端渲染首屏渲染快?(对比客户端首屏渲染) react客户端渲染的一大痛点就是首屏渲染速度慢问题,因为react是一个单页面应用,大多数的资源需要在首次渲染前就加载 ...
- 【redux】详解react/redux的服务端渲染:页面性能与SEO
亟待解决的疑问 为什么服务端渲染首屏渲染快?(对比客户端首屏渲染) react客户端渲染的一大痛点就是首屏渲染速度慢问题,因为react是一个单页面应用,大多数的资源需要在首次渲染前就加载 ...
- react + redux 完整的项目,同时写一下个人感悟
先附上项目源码地址和原文章地址:https://github.com/bailicangd... 做React需要会什么? react的功能其实很单一,主要负责渲染的功能,现有的框架,比如angula ...
随机推荐
- POJ1144 Network 无向图割点
题目大意:求以无向图割点. 定义:在一个连通图中,如果把点v去掉,该连通图便分成了几个部分,则v是该连通图的割点. 求法:如果v是割点,如果u不是根节点,则u后接的边中存在割边(u,v),或者v-&g ...
- 关于static和const
先谈一下static, 它是一个存储修饰变量.被static修饰的变量存储在静态数据区,只初始化一次,保持数据的持久性.被static修饰的变量和函数有一个共同点是对其他的源文件不可见.被static ...
- BigDecimal快速使用
double类型最多支持16位有效数字,且最大值只支持10^308次方,大一点的数字会变为科学计数法,小数精度不够等有一系列不方便的问题: 引进BigDecimal解决此类麻烦,弊端,BigDecim ...
- [RK3288][Android6.0] 调试笔记 --- 系统识别不同硬件版本方法【转】
本文转载自:http://m.blog.csdn.net/kris_fei/article/details/70226451 Platform: RockchipOS: Android 6.0Kern ...
- ⭐驱动之module_init/module_exit与系统启动关系
在前面helloworld的编写里面,我们使用了两个宏分别是module_init和module_exit,这里分析下为什么使用这两个宏. 在写模块的时候有两个特殊的函数,分别是init_module ...
- 织梦dedecms页面中增加二维码功能的实现方法
本文介绍了在dedecms中增加二维码功能的实现方法,有时需要在dedecms页面增加二维码,方便手机用户访问,有需要的朋友参考下. 本节内容: dedecms中增加二维码功能 1.打开/incl ...
- 怎么往mac中finder个人收藏里添加文件夹
1.打开Finder,点击左上角finder偏好设置 2.选择边栏 3.如果侧栏中没有的文件夹,直接长按文件夹直接拖入.
- SPOJ:Another Version of Inversion(二维数组的逆序对)
DCE Coders admins are way much geekier than they actually seem! Kartik has been following that tradi ...
- macbook pro 一些操作经验记录
[一]启用root用户 (1)打开终端 (2)输入命令:sudo passwd root (3)给root设置密码 (4)然后再终端输入命令:su root 进行登陆 [二]解压eclipse压缩包, ...
- angularJS ng-bind用法
ng-bind 指令绑定控制器函数 函数名() 到 标签里面 ; ng-bind是从$scope -> view的单向绑定ng-modle是$scope <-> view的双向绑定; ...