From the very beginning, we need to stress that Redux has no relation to React. You can write Redux apps with React, Angular, Ember, jQuery, or vanilla JavaScript.
我们经常将redux与react两者放在一起谈论,但实际上Redux与React并没有直接关联,也没有相互依赖的。Redux本质上是一个状态容器,给予开发者维护状态的能力,而这恰恰是React的薄弱环节。
在仅有React的开发中,我们不得不将组件的状态存入state中,子组件需要通过父组件给予的回调函数修改父组件的state,父组件需要把状态通过props传递给子组件。两个组件间需要通信(也可以理解为两个组件需要相互修改状态),就需要将state存储在他们共有的父组件中。随着应用越来越复杂,父组件的state越来越大,传给子组件的props也越来越复杂(即包含数据, 又有回调函数)。
此时Redux出现了,Redux单一数据流的思想和react是不谋而合的。我们可以将整个父组件的state放入Redux的store中进行维护。但我们在本文开头提到过,Redux与React之间是没有关联的,那么如何将Redux与React串联起来呢? 我们使用了react-redux。
本质
首先我们先思考一下,我们期望react-redux做一些什么事情?
毫无疑问,一定是构建起Redux与React的桥梁,我们将React中的state提升到Redux的store中,期望在React的view层中能够触发改变Redux store的操作,就像原来的setState一样,我们也期望React组件能够感应到store的change,完成view的重绘。
react-redux也就是这样做的,它非常简洁,仅仅对外暴露了两个API,<Provider store>
与connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
,API其实非常清晰地给予了顶层组件与子组件的connect方式,如下图:
Provider
Provider的作用就在于获取props中的store,将store存储于context中(源码注释1-1),这样所有的后代元素均可以获取到顶层的store,摆脱了父子元素间props的传递。注意,Provider只能包含一个子元素(源码注释1-2),正如上图所示一样,只有一个Root Component
。
|
|
Connect
对于一个组件而言, 我们不仅仅需要store的state中所存储的数据,需要获得父组件的props,父组件的props可能是一些数据,也可能是一些配置,也需要获得能够触发store中state改变的action。
那connect内部究竟是怎么实现的呢?让我们看一下源码:
connect参数
|
|
connect先处理第一个参数mapStateToProps
,是否定义该参数,未定义将用defaultMapStateToProps
,这将使storeState对组件失去意义,因为默认返回空对象。shouldSubscribe
将在之后被用到,可以先放在一边(源码注释2-1)
。
接下来处理mapDispatchToProps
,它会有三种情况(源码注释2-2)。
- 当参数为一个函数时,
mapDispatch
即为参数函数,会被传入两个参数,store.dispatch与props。
|
|
- 当参数未定义时,会返回store.dispatch,向组件的props传入,此时往往action都是通过外部引入的,注意:直接调用action是无法改变storeState,引发reduce的,需要用store.dispatch绑定或执行action。
|
|
- 当参数为函数对象时,默认使用
bindActionCreators
将对象中的函数与dispatch进行绑定。
|
|
第三个参数mergeProps
的默认值将mapStateToProps
, mapDispatchToProps
与ownProps
三者合并,交给connect的组件(源码注释2-3)。
第四个参数option
包含两个值,pure
与withRef
,pure
为true的情况下,在connect
内部会帮助我们做一些props
的浅比较,避免不必要的更新,比如: 比较finalMergeProps
与defaultMergeProps
的结果。withRef
会在之后的源码分析中会再详细讲解。
wrapWithConnect
|
|
在wrapWithConnect中,首先会从props或context中获取到store存入this.store,将stroe中的state存入this.state,这对之后判断是非更新都会起到非常重要的作用。
|
|
通过trySubscribe
,我们会利用this.store.subscribe
对每一次action操作做监听,执行handleChange操作。当shouldSubscribe
(源码2-1)为false,即mapStateToProps未传入时,就会失去对storeState变化的感知。
|
|
我们主要关注于每一次action后storeState的变化,这里就会使用到之前存在this.state中的storeState,它存储了上一个storeState的值。
mapStateToProps可以接受state(storeState)、 props(ownProps)两个参数,它的返回值stateProps会受到storeState与ownProps双重影响。在这里我们只能知道在mapStateToProps的参数中是否定义了ownProps,却无法得知ownProps是否发生了变化。ownProps发生变化的处理会在其他地方完成,这里专注于storeState对stateProps的影响,可能会让人有种残缺感。
如果没有变化(return),维持this.hasStoreStateChanged
默认值false。stateProps将由是否在mapStateToProps定义props参数(this.doStatePropsDependOnOwnProps
)与props是否变化所决定。
如果发生变化,且mapStateToProps未定义props参数,也就是stateProps完全受storeState控制。我们就可以直接得出stateProps是否改变,以及stateProps改变后的结果了。
|
|
我们现在需要得出mapStateToProps的返回值stateProps,向computeStateProps
传入this.store
与this.props
,finalMapStateToProps
是传入storeState和props的函数,mapStateToProps所返回的可能是对象,也可能是函数,利用函数来进行一些优化。在react-redux中采用了非常多函数式编程的思想,参数值保持一致,在没有外部依赖的情况下,所得到的结果必然一致。
对于stateProps
,dispatchProps
与ownProps
三者而言,都有统一的更新入口,分别为updateStatePropsIfNeeded
,updateDispatchPropsIfNeeded
与updateMergedPropsIfNeeded
。它们实现思想是一致的,将上次的结果存放在this当中,与本次的结果进行比较,以此来避免不必要的更新。
|
|
当pure不为true, 父组件传入props发生变化(componentWillReceiveProps
判断)以及storeState发生变化(this.store.subscribe(this.handleChange.bind(this))
,handleChange判断),都在shouldComponentUpdate
前执行。
|
|
在render中主要还是判断是否需要更新,然后将一些标记初始化,待下一次更新时使用。在updateMergedPropsIfNeeded
中将stateProps
, dispatchProps
与ownProps
合并在一起,作为WrappedComponent
的props传入,这样我们就可以在子组件的props中拿到想要的东西了。
withRef
会向子组件传递ref
值,通过函数getWrappedInstance
可进行引用。
ES7 Decorator
connect的常规写法:
|
|
使用Decorator会使代码更加清晰,connect主要作用就是store与component的连接,作为一个中间层,处理上层给予的storeState,父组件props与actions,整合后作为props给予下层组件。
Decorator对类行为的改变,在代码编译时发生,本质上就是在编译时执行的函数。
|
|
写了一个简单模拟coonect的示例,Decorator将类作为参数传入,处理完成之后再返回,相当于外层的一次包裹。