react-redux高阶组件connect方法使用介绍以及实现原理

redux

这里写图片描述

connect之前先来回顾一下redux的基本用法, 见下面的例子:

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
import { createStore } from 'redux';

function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}

// 创建 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);

// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() =>
console.log(store.getState())
);

// 改变内部 state 惟一方法是 dispatch 一个 action。
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1

以上总结起来就只有下面四个方法

1
2
3
4
let store = createStore(reducer); // 创建store
store.getState( ); // 获取state值
store.dispatch({ type: "text" }); // 使用action更改在reducer中定义好的更改store的更改策略
store.subScribe(render); // 设置监听函数, 在更改store之后触发

关于redux的更多内容, 可以参阅官方文档.
好了, 进入正题


react-redux 高阶组件 connect方法介绍以及实现原理

在使用介绍connect之前, 先简单介绍一下什么是高阶组件.




高阶组件

高阶组件就是一个函数,传给它一个组件,它返回一个新的组件。 实际上就是一个类工厂, 见下面的一个例子.

1
2
3
4
5
6
7
8
9
10
11
import React, { Component } from 'react'

export default (People) => {
class Star extends Component {
// 可以做很多自定义逻辑
render () {
return <People/>
}
}
return Star
}

这就好比一个普通的人, 经过公司的包装之后, 变成一个会很多种技能的明星一样.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { Component } from 'react'

export default (People, things) => {
class Star extends Component {
constructor () {
super()
this.state = { data: null }
}

componentWillMount () {
ajax.get('/data/' + things, (data) => {
this.setState({ data })
})
}

render () {
return <People data={this.state.data} />
}
}
return Star
}

People推上市场之前, 先告诉公司他需要什么things, 然后公司在componentWillMount阶段对他需要的things进行准备, 最后返回一个Star




React.js 的 context

还要再说一个小概念, 如果不提这个无法讲述connect的实现原理, 因此如果不想看原理只想看使用方法的话, 这一部分可以跳过, 不过博主会用简单的语言描述这个问题, 最好看一下.ヾ(◍°∇°◍)ノ゙

某个组件只要往自己的 context 里面放了某些状态,这个组件之下的所有子组件都直接访问这个状态而不需要通过中间组件的传递。一个组件的 context 只有它的子组件能够访问,它的父组件是不能访问到的,你可以理解每个组件的 context 就是瀑布的源头,只能往下流不能往上飞。
那么怎么设置context呢? 见下面的代码

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
class Parent extends Component { // 父组件
static childContextTypes = {
tags: PropTypes.string
}

constructor () {
super()
this.state = { tags: 'hello' }
}

getChildContext () {
return { index: this.state.tags }
}

render () {
return (
<div>
<Title />
</div>
)
}
}

class Title extends Component {
static contextTypes = {
tags: PropTypes.string
}

render () {
return (
<h1>{ this.context.tags }</h1>
)
}
}

总的来说:
一个组件可以通过 getChildContext 方法返回一个对象, 这个对象就是子树的 context, 提供 context 的组件必须提供 childContextTypes 作为 context 的声明和验证.

如果一个组件设置了 context, 那么它的子组件都可以直接访问到里面的内容, 它就像这个组件为根的子树的全局变量。任意深度的子组件都可以通过 contextTypes 来声明你想要的 context 里面的哪些状态, 然后可以通过 this.context 访问到那些状态.




connect

1
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

connect有四个参数, 但是后两个参数用到的很少, 所以本篇博客之探讨前两个参数.

connect 的第一个参数是 mapStateToProps

这个函数允许我们将 store 中的数据作为 props 绑定到组件上

1
2
3
4
5
6
const mapStateToProps = (state) => { // 正常我们在react-redux中会这样书写
return {
themeColor: state.themeColor
}
}
People = connect(mapStateToProps)(People) // connect返回来的是一个函数, 因此还要再一次调用传入组件

实现主要原理, 就是将需要绑定的props作为一个函数传过来, 在connect中传给mapStateToProps一个真实的store的数据

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
const connect = (mapStateToProps) => (People) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}

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

componentWillMount () {
const { store } = this.context
this.setProps()
}

setProps () {
const { store } = this.context
let stateProps = mapStateToProps(store.getState(), this.props) // 额外传入 props
this.setState({
allProps: { // 整合普通的 props 和从 state 生成的 props
...stateProps,
...this.props
}
})
}

render () {
return <People {...this.state.allProps} />
}
}
return Connect
}

connect 的第二个参数是 mapDispatchToProps

由于更改数据必须要触发action, 因此在这里的主要功能是将 action 作为props 绑定到 组件上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const mapDispatchToProps = (dispatch, ownProps) => {
return {
increase: (...args) => dispatch(actions.increase(...args)),
decrease: (...args) => dispatch(actions.decrease(...args))
}
}

class People extends Component {
render(){
const {count, increase, decrease} = this.props;
return (<div>
<div>计数:{this.props.count}次</div>
<button onClick={increase}>增加</button>
<button onClick={decrease}>减少</button>
</div>)
}
}

const NiuPeople = connect(mapStateToProps, mapDispatchToProps)(People);

这里的实现原理和上面的相差不多, 主要是将actionprops一起传到组件里.

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
const connect = (mapStateToProps, mapDispatchToProps) => (People) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}

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

componentWillMount () {
const { store } = this.context
this.setProps()
store.subscribe(() => this.setProps())
}

setProps () { // 做了一下完整性的判断
const { store } = this.context
let stateProps = mapStateToProps
? mapStateToProps(store.getState(), this.props)
: {} // 防止 mapStateToProps 没有传入
let dispatchProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, this.props)
: {} // 防止 mapDispatchToProps 没有传入
this.setState({
allProps: {
...stateProps,
...dispatchProps,
...this.props
}
})
}

render () {
return <People {...this.state.allProps} />
}
}
return Connect
}

Provider

这是最后一个要说的问题, 讲到这里可能有一个疑问, 就是context是什么时候设置的呢. 下面要说的就是这个问题.

Provider就是react-redux中的一个组件, Provider 做的事情也简单, 它就是一个容器组件, 会把嵌套的内容原封不动作为自己的子组件渲染出来. 它还会把外界传给它的 props.store 放到 context, 这样子组件 connect 的时候都可以获取到. 见下面代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Provider extends Component {
static propTypes = {
store: PropTypes.object,
children: PropTypes.any
}

static childContextTypes = {
store: PropTypes.object
}

getChildContext () {
return {
store: this.props.store
}
}

render () {
return (
<div>{this.props.children}</div>
)
}
}

参考地址:
作者: 胡子大哈 http://huziketang.com/books/react/lesson40
作者: 沈斯明 http://blog.csdn.net/u010977147/article/details/53412381

越来越多的平台(微信公众平台,新浪微博,简书,百度打赏等)支持打赏功能,付费阅读时代越来越近,特此增加了打赏功能,支持微信打赏和支付宝打赏。坚持原创技术分享,您的支持将鼓励我继续创作!