我们来观察一下刚写下的这几个组件,可以轻易地发现它们有两个重大的问题:

  1. 有大量重复的逻辑:它们基本的逻辑都是,取出 context,取出里面的 store,然后用里面的状态设置自己的状态,这些代码逻辑其实都是相同的。
  2. 对 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的更多相关文章

  1. react+redux教程(一)connect、applyMiddleware、thunk、webpackHotMiddleware

    今天,我们通过解读官方示例代码(counter)的方式来学习react+redux. 例子 这个例子是官方的例子,计数器程序.前两个按钮是加减,第三个是如果当前数字是奇数则加一,第四个按钮是异步加一( ...

  2. [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 ...

  3. react+redux教程(三)reduce()、filter()、map()、some()、every()、...展开属性

    reduce().filter().map().some().every()....展开属性   这些概念属于es5.es6中的语法,跟react+redux并没有什么联系,我们直接在https:// ...

  4. webpack+react+redux+es6开发模式

    一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入 ...

  5. angular开发者吐槽react+redux的复杂:“一个demo证明你的开发效率低下”

    曾经看到一篇文章,写的是jquery开发者吐槽angular的复杂.作为一个angular开发者,我来吐槽一下react+redux的复杂. 例子 为了让大家看得舒服,我用最简单的一个demo来展示r ...

  6. webpack+react+redux+es6

    一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入 ...

  7. react+redux渲染性能优化原理

    大家都知道,react的一个痛点就是非父子关系的组件之间的通信,其官方文档对此也并不避讳: For communication between two components that don't ha ...

  8. 详解react/redux的服务端渲染:页面性能与SEO

        亟待解决的疑问 为什么服务端渲染首屏渲染快?(对比客户端首屏渲染)   react客户端渲染的一大痛点就是首屏渲染速度慢问题,因为react是一个单页面应用,大多数的资源需要在首次渲染前就加载 ...

  9. 【redux】详解react/redux的服务端渲染:页面性能与SEO

        亟待解决的疑问 为什么服务端渲染首屏渲染快?(对比客户端首屏渲染)   react客户端渲染的一大痛点就是首屏渲染速度慢问题,因为react是一个单页面应用,大多数的资源需要在首次渲染前就加载 ...

  10. react + redux 完整的项目,同时写一下个人感悟

    先附上项目源码地址和原文章地址:https://github.com/bailicangd... 做React需要会什么? react的功能其实很单一,主要负责渲染的功能,现有的框架,比如angula ...

随机推荐

  1. JS中使用组合构造函数模式和原型模式

    创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式.构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性. 结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的 ...

  2. 大O符号

    大O 就是 时间复杂度.时间复杂度是大概的描述一个算法的用时(实际上从侧面的表达了他的效率) 最好时间复杂度,通常指在最好情形下,这个算法用时.反之,最坏情况下的就是最坏时间复杂度.通常 冒泡算法 的 ...

  3. ADB结构及代码分析【转】

    本文转载自:http://blog.csdn.net/happylifer/article/details/7682563 最近因为需要,看了下adb的源代码,感觉这个作者很牛,设计的很好,于是稍微做 ...

  4. POJ 2017 Speed Limit (直叙式的简单模拟 编程题目 动态属性很少,难度小)

                                                                                                      Sp ...

  5. 【BZOJ 3223】 文艺平衡树

    [题目链接] 点击打开链接 [算法] 本题是splay区间操作的模板题 我们每个点的权值设为”当前在序列中的排名“,根据二叉排序树的性质,这棵树的中序遍历就是当前序列 如果我们要获得一段区间[l,r] ...

  6. 在msys里进行复制和粘贴操作

    You can copy text from an MSYS window to the clipboard simply by selecting the text you want to copy ...

  7. 详解Redis Cluster集群

    Redis Cluster是Redis的分布式解决方案,在Redis 3.0版本正式推出的,有效解决了Redis分布式方面的需求.当遇到单机内存.并发.流量等瓶颈时,可以采用Cluster架构达到负载 ...

  8. 【WEB基础】HTML & CSS 基础入门(4)列表及其样式

    前面 网页中漂亮的导航.整齐规范的文章标题列表和图片列表等等.这些都是离不开HTML里一个重要的元素----列表,在HTML中有无序列表.有序列表和定义列表三种类型.其中,无序列表应用最为广泛,下面, ...

  9. 洛谷 - P1141 - 01迷宫 - dfs

    https://www.luogu.org/problemnew/show/P1141 能互相到达的格子的答案自然是一样的,第一次dfs标记联通块,第二次dfs把cnt传递到整个联通卡并顺手消除vis ...

  10. Codeforces - 1114C - Trailing Loves (or L'oeufs?) - 简单数论

    https://codeforces.com/contest/1114/problem/C 很有趣的一道数论,很明显是要求能组成多少个基数. 可以分解质因数,然后统计各个质因数的个数. 比如8以内,有 ...