子组件直接利用 context 的问题

第三节我们实现了子组件利用 context 属性,通过统一的修改方式对全局 state 进行修改,同时利用发布者-订阅者模式实现了数据更新的监听及界面重新渲染。

但是仍然存在两个问题:

  1. 子组件都是利用 context 属性来取得 store 对象,进而对全局状态 state 进行修改,这样会导致很多重复性代码

  2. 每个子组件都依赖了 context 属性,导致子组件与父组件形成了强耦合,不利于组件的复用。例如,前面的 Context 组件需要父组件定义 context 属性,否则主题颜色将无法设置。

解决方法

  1. 对于重复使用的代码,可以凝练提取出一个可复用的代码结构。这里可以通过高阶组件(或者装饰器模式)将 context 的控制与相关事件监听代理起来。

  2. 对于强耦合问题,可以通过设置 Pure Component,即在高阶组件内部的被装饰组件尽量设计成纯组件。纯组件是其状态仅依赖于父组件传入的 props 以及 自己的 state,对于外界依赖极低,因此复用性极高。

抽离出公共的逻辑代码

我们可以设计一个 connect 函数,该函数的目标是创建一个高阶组件。connect 函数接收两个参数,一个是 mapState2Props 函数,一个是原始的被修饰组件 OriginComponent。

  • mapState2Props函数负责从父组件接收 state 并将其转化为 props 传递给被修饰组件 OriginComponent,实现 OriginComponent 与父组件 context 之间的解耦以及公共逻辑代码的抽离。

  • OriginComponent 是原始的子组件,例如 Context 组件。

新建 react-redux.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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// src/react-redux.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'

export const connect = (mapState2Props)=>(OriginComponent)=>{
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}

constructor(){
this.state = {
allProps: {}
}
}

componentWillMount(){
this._updateProps()
}

_updateProps(){
const {store} = this.context.store
const stateProps = mapState2Props(store.getState())
this.setState({
allProps: {
...stateProps,
...this.props
}
})
// 绑定订阅者监听函数
store.subscribe(this._updateProps.bind(this))
}

render() {
return (
<div>
<OriginComponent {...this.state.allProps}/>
</div>
)
}
}

return Connect
}

然后再 src/Content.jssrc/Header 文件中利用 Connect 高阶函数实现全局变量管理:

src/Header

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

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 (
<div>
<h1 style={{color: this.props.themeColor}}>动手实现React-Redux</h1>
</div>
)
}
}

const mapState2Props = (state)=>{
return {
themeColor: state.themeColor
}
}

Header = connect(mapState2Props)(Header)

export default Header;

src/Content

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
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-Redux 的内容</p>
<ThemeSwitch/>
</div>
)
}
}

const mapState2Props = (state)=>{
return {
themeColor: state.themeColor
}
}

Content = connect(mapState2Props)(Content)

export default Content

下一节将对 ThemeSwitch 组件进行重构