文章目录
redux
讲connect
之前先来回顾一下redux
的基本用法, 见下面的例子:
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
以上总结起来就只有下面四个方法
let store = createStore(reducer); // 创建store
store.getState( ); // 获取state值
store.dispatch({ type: "text" }); // 使用action更改在reducer中定义好的更改store的更改策略
store.subScribe(render); // 设置监听函数, 在更改store之后触发
关于redux
的更多内容, 可以参阅官方文档.
好了, 进入正题
react-redux 高阶组件 connect方法介绍以及实现原理
在使用介绍connect
之前, 先简单介绍一下什么是高阶组件.
高阶组件
高阶组件就是一个函数,传给它一个组件,它返回一个新的组件。 实际上就是一个类工厂, 见下面的一个例子.
import React, { Component } from 'react'
export default (People) => {
class Star extends Component {
// 可以做很多自定义逻辑
render () {
return <People/>
}
}
return Star
}
这就好比一个普通的人, 经过公司的包装之后, 变成一个会很多种技能的明星一样.
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
呢? 见下面的代码
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
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
connect
有四个参数, 但是后两个参数用到的很少, 所以本篇博客之探讨前两个参数.
connect 的第一个参数是 mapStateToProps
这个函数允许我们将 store 中的数据作为 props 绑定到组件上
const mapStateToProps = (state) => { // 正常我们在react-redux中会这样书写
return {
themeColor: state.themeColor
}
}
People = connect(mapStateToProps)(People) // connect返回来的是一个函数, 因此还要再一次调用传入组件
实现主要原理, 就是将需要绑定的props
作为一个函数传过来, 在connect
中传给mapStateToProps
一个真实的store
的数据
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
绑定到 组件上
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);
这里的实现原理和上面的相差不多, 主要是将action
和props
一起传到组件里.
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
的时候都可以获取到. 见下面代码.
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.youkuaiyun.com/u010977147/article/details/53412381