动手实现react-redux之三connect 和 mapStateToProps

仔细看上一节完成的组件会发现两个问题:

  1. 每个组件中都有大量重复获取 context ,添加 store 监听的逻辑
  2. 每个组件都依赖于 context ,使组件基本丧失了可复用性。

对于第一个大量重复逻辑的问题,可以通过高阶组件抽取重复的逻辑来解决。高阶组件就是一个函数,传给它一个组件,它返回一个新的组件,它的作用就是用于代码复用,可以把组件之间可复用的代码、逻辑抽离到高阶组件当中。新的组件和传入的组件通过 props 传递信息

对于第二个问题,首先需要知道可复用组件需要具有什么样的特征,在 React 中,如果一个组件的渲染只依赖于外界传进去的 props 和自己的 state,而并不依赖于其他的任何外界数据,也就是说像纯函数一样,给它什么,它就吐出(渲染)什么出来。这种组件的复用性是最强的,别人使用的时候根本不用担心任何事情,只要看看 PropTypes 它能接受什么参数,然后把参数传进去控制它就行了。

有了思路,下面就来修改代码,首先需要一个高阶组件来协助从 context 中获取数据,然后用一个傻瓜组件来帮助提交组件的复用性。

将这个高阶组件命名为 connect,他的作用是将 context 和 可复用组件连接起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { Component } from "react";
import PropTypes from "prop-types";

export default 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 组件以外,还需要告诉高级组件我们需要什么数据,高阶组件才能正确地去取数据。为了解决这个问题,我们可以给高阶组件传入类似下面这样的函数:

1
2
3
4
5
6
7
8
const 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
6
const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
};
};
Header = connect(mapStateToProps)(Header);

现在根据上面的描述,给 connect 加上 mapStateToProps 和 数据变化的监听,connect 完整的代码应该是:

1
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import React from "react";
import PropTypes from "prop-types";

function connect(mapStateToProps) {
return (WrappedComponent) => {
class Connect extends React.Component {
static contextTypes = {
store: PropTypes.object
};

state = {
allProps: {}
};

componentWillMount() {
const { store } = this.context;
// 挂载的时候,进行第一次渲染
this.updateProps();
// 然后订阅 store 的后续变化,并更新
store.subscribe(() => this.updateProps());
}

updateProps() {
const { store } = this.context;
const state = store.getState();
// 额外传入 props,让获取数据更加灵活方便
let stateProps = mapStateToProps(state, this.props);
this.setState({
// 整合普通的 props 和从 state 生成的 props
allProps: {
...this.props,
...stateProps
}
});
}

render() {
// let { } = this.props;
return (
<WrappedComponent {...this.state.allProps} />
);
}
}

return Connect;
};
}

export default connect;

组件 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.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import 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.js

1
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
28
import 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 组件。

参考

http://huziketang.mangojuice.top/books/react/