在谈 react-redux 之前,我们先来回顾一下 react 组件的通信方式:
- 通过 props 父传子,子传孙。缺点是层级较深的话,传递会比较麻烦。消息横向传递很麻烦。
- 消息订阅发布模式,需要自己实现监听和触发这一过程。消息可以同级横向传递,如 A 和 B 都是 C 的子组件,可以在 C 组件中调度 A、B 的监听函数。
- 通过 react 中的上下文对象 context 来传递消息,由于 context 全局独一份,所以可以实现跨层级、横向的消息传递
我们来看一下第三种方式,context 的使用姿势
class Man extends React.Component {
getChildrenContext () {
return {
num: 100
}
}
render () {
<div>
<Son>
<Grandson></Grandson>
</Son>
</div>
}
}
Man.childContextTypes = {
num: React.Prototypes.number
}
class Grandson extends React.Component {
render () {
return (
<div>{ this.context.num }</div>
)
}
}
Grandson.contextTypes = {
num: React.Prototypes.number
}
可以看到 Man 组件中的 num 是可以跨层级传递给孙子组件 Grandson 的,而 react-redux 正是利用了这一点,基于 context 实现了数据的共享。
话不多说,先上代码,看一下 react-redux 怎么用,还是以上一篇文章【前端架构系列—React 全家桶之 redux】中的代码为例。reducer 和 store 都不用变,只是 component 改一下就行了。
这也好理解,reducer 和 store 是纯 redux 的部分,而 react-redux 是 react 和 redux 之间桥梁,所以要引入 react-redux,只要在原先 react 中实接使用 redux 的地方动动刀子就行了。话不多说先上代码
src/App.js,将项目顶层组件封装在 Provider 组件内,并将 store 对象做为其属性。这样一来,所有的子组件都可以拿到 store 中的数据
import { Provider } from 'react-redux';
import store from './store';
class App extends React.Component {
render() {
return (
<Provider store={ store }>
<Counter />
</Provider>
);
}
}
export default App;
components/counter.js,有如下几点的改变
- 输入:原先通过 store.getState() 拿到数据,现在变成了通过 mapStateToProps,将 store 中的数据挂载到组件的 props 上
- 输出:原先通过 store.dispatch() 触发 action,现在变成了通过 mapDispatchToProps,将 actionsCreator 中的方法挂载到组件的 props 上。且这些 actionCreator 经过 bindActionCreators 封装之后具备了 dispatch 的功能。这样一来,在组件中可以直接调用 this.props.action() 来触发一个 action
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import actionCreator from '../actions/counter'
class Counter extends React.Component {
render() {
const { num, actions } = this.props;
return (
<div className="App">
{
num
}
<div></div>
<button onClick={ actions.increment }>+</button>
<button onClick={ actions.decrement }>-</button>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return {
num: state.counter.num
}
}
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(actionCreator, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
整个过程用到了几个重要的 API
Provider:顶层组件,作用是把 store 挂载到 context 上,以便下层组件可以从 context 或者 props 中获取 store
connect:高阶组件,获取到 context 上的 store 以及 actionCreator,然后封装业务组件,以属性的方式传递给业务组件
mapStateToProps:selector 选择器,从 store 中的选出组件需要的值,做为 selector 的返回值
mapDispatchToProps:selector选择器,提取某些 actionCreator,做为 selector 的返回值
下面分别大致说一下每个方法的实现过程
Provider 组件
Provider 组件的作用就是把 store 挂载到 context 上面,把项目的根组件包裹起来,这样任意一个业务组件都可以通过 context 拿到 store 中的数据
class Provider extends React.Component {
getChildContext () {
return {
store: this.props.store
}
}
render () {
return Children.only(this.props.children)
}
}
Provider.propTypes = {
store: PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired
}),
context: PropTypes.object,
children: PropTypes.any
}
connect 函数
connect 是一个高阶组件,它接收 mapStateToProps、mapDispatchToProps、mergeProps、options 作为参数。它的作用就是将 store 中的 state 和 dispatch 映射到组件的 props 上。这样组件可以轻易的获取 state 和 发出 action.
render() {
const selector = this.selector
selector.shouldComponentUpdate = false
if (selector.error) {
throw selector.error
} else {
return createElement(WrappedComponent, this.addExtraProps(selector.props))
}
}
mapStateToProps
store 中的 state 是整个项目的数据,而单个业务组件只需要这颗 state 树中的某几个数据。所以该对象的作用就是摘取组件所需要的几个 state。
可以看到如果我们没有传 mapStateToProps 这个参数给 connect 函数的话,connect 组件并不会监听 state 的变化来更新组件。
// if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes
shouldHandleStateChanges: Boolean(mapStateToProps),
相反,如果传了就会初始化一些监听
if (!shouldHandleStateChanges) return
const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}
mapDispatchToProps
改对象的传值有三种情况:
- function:
- Object:这时会调用 redux 的 bindActionCreators,将这个 actionCreator 对象封装起来,是这个 creator 被调用的时候,直接就能 dispatch
- 不传
export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
return (typeof mapDispatchToProps === 'function')
? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps')
: undefined
}
export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
return (!mapDispatchToProps)
? wrapMapToPropsConstant(dispatch => ({ dispatch }))
: undefined
}
export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
return (mapDispatchToProps && typeof mapDispatchToProps === 'object')
? wrapMapToPropsConstant(dispatch => bindActionCreators(mapDispatchToProps, dispatch))
: undefined
}