仔细看上一节完成的组件会发现两个问题:
- 每个组件中都有大量重复获取 context ,添加 store 监听的逻辑
- 每个组件都依赖于 context ,使组件基本丧失了可复用性。
对于第一个大量重复逻辑的问题,可以通过高阶组件抽取重复的逻辑来解决。高阶组件就是一个函数,传给它一个组件,它返回一个新的组件,它的作用就是用于代码复用,可以把组件之间可复用的代码、逻辑抽离到高阶组件当中。新的组件和传入的组件通过 props 传递信息
对于第二个问题,首先需要知道可复用组件需要具有什么样的特征,在 React 中,如果一个组件的渲染只依赖于外界传进去的 props 和自己的 state,而并不依赖于其他的任何外界数据,也就是说像纯函数一样,给它什么,它就吐出(渲染)什么出来。这种组件的复用性是最强的,别人使用的时候根本不用担心任何事情,只要看看 PropTypes 它能接受什么参数,然后把参数传进去控制它就行了。
有了思路,下面就来修改代码,首先需要一个高阶组件来协助从 context 中获取数据,然后用一个傻瓜组件来帮助提交组件的复用性。
将这个高阶组件命名为 connect,他的作用是将 context 和 可复用组件连接起来。
1 | import React, { Component } from "react"; |
connect 函数接受一个组件 WrappedComponent 作为参数,把这个组件包含在一个新的组件 Connect 里面,Connect 会去 context 里面取出 store。现在要把 store 里面的数据取出来通过 props 传给 WrappedComponent。
但是每个传进去的组件需要 store 里面的数据都不一样的,所以除了给高阶组件传入 Dumb 组件以外,还需要告诉高级组件我们需要什么数据,高阶组件才能正确地去取数据。为了解决这个问题,我们可以给高阶组件传入类似下面这样的函数:1
2
3
4
5
6
7
8const mapStateToProps = (state,props) => {
return {
themeColor: state.themeColor,
themeName: state.themeName,
fullName: `${state.firstName} ${state.lastName}`
...
};
};
这个函数会接受 store.getState() 的结果和给 WrappedComponent 传递的 props 作为参数,然后返回一个对象。mapStateTopProps 相当于告知了 Connect 应该如何去 store 里面取数据,然后可以把这个函数的返回结果传给被包装的组件。
connect 现在是接受一个参数 mapStateToProps,然后返回一个函数,这个返回的函数才是高阶组件。它会接受一个组件作为参数,然后用 Connect 把组件包装以后再返回。 connect 的用法是:1
2
3
4
5
6const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
};
};
Header = connect(mapStateToProps)(Header);
现在根据上面的描述,给 connect 加上 mapStateToProps 和 数据变化的监听,connect 完整的代码应该是:
1 | import React from "react"; |
组件 Connect 的 state.allProps,它是一个对象,用来保存需要传给被包装组件的所有的参数。生命周期 componentWillMount 会调用调用 updateProps 进行初始化,然后通过 store.subscribe 监听数据变化重新调用 updateProps。
为了让 connect 返回新组件和被包装的组件使用参数保持一致,我们会把所有传给 Connect 的 props 原封不动地传给 WrappedComponent。所以在 updateProps 里面会把 stateProps 和 this.props 合并到 this.state.allProps 里面,再通过 render 方法把所有参数都传给 WrappedComponent。
现在使用 connect 修改 Header.js、Content.js。
src/Header.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import React, { Component } from "react";
import PropTypes from "prop-types";
import connect from "./connect";
class Header extends Component {
static propTypes = {
color: PropTypes.string
};
render() {
return (
<h1 style={{ color: this.props.themeColor }}>React-Redux是什么</h1>
);
}
}
function mapStateToProps(state, props) {
return {
themeColor: state.themeColor
};
}
export default connect(mapStateToProps)(Header);
src/Content.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28import React, { Component } from "react";
import PropTypes from "prop-types";
import connect from "./connect";
import ThemeSwitch from "./ThemeSwitch";
class Content extends Component {
static propTypes = {
themeColor: PropTypes.string
};
render() {
return (
<div style={{ color: this.props.themeColor }}>
<p>React-Redux是Redux的官方React绑定库。它能够使你的React组件从Redux store中读取数据,并且向store分发actions以更新数据</p>
<ThemeSwitch />
</div>
);
}
}
function mapStateToProps(state) {
return {
themeColor: state.themeColor
};
}
export default connect(mapStateToProps)(Content);
现在通过 connect 抽取了使用 context 产生的重复逻辑,并提高了 Header 和 Context 组件的复用性,后面继续重构 ThemeSwitch 组件。