以前写了一篇分析Redux中Store实现的文章(详见:Redux原理(一):Store实现分析),忽然意识到,其实React与Redux并无什么直接的联系。Redux做为一个通用模块,主要仍是用来处理应用中state的变动,而展现层不必定是React。
但当咱们但愿在React+Redux的项目中将二者结合的更好,能够经过react-redux作链接。
本文结合react-redux的使用,分析其实现原理。html
react-redux是一个轻量级的封装库,核心方法只有两个:react
下面咱们来逐个分析其做用git
完整源码请戳这里
Provider模块的功能并不复杂,主要分为如下两点:github
下面看下具体代码:
编程
[31-34] render方法中,渲染了其子级元素,使整个应用成为Provider的子组件。
一、this.props.children
是react内置在this.props
上的对象,用于获取当前组件的全部子组件
二、Children
为react内部定义的顶级对象,该对象上封装了一些方便操做子组件的方法。Children.only
用于获取仅有的一个子组件,没有或超过一个均会报错。故须要注意:确保Provider组件的直接子级为单个封闭元素,切勿多个组件平行放置。redux
[26-29] Provider初始化时,获取到props中的store对象;
[22-24] 将外部的store对象放入context对象中,使子孙组件上的connect能够直接访问到context对象中的store。
一、context
可使子孙组件直接获取父级组件中的数据或方法,而无需一层一层经过props向下传递。context
对象至关于一个独立的空间,父组件经过getChildContext()
向该空间内写值;定义了contextTypes
验证的子孙组件能够经过this.context.xxx
,从context对象中读取xxx字段的值。缓存
总而言之,Provider模块的功能很简单,从最外部封装了整个应用,并向connect模块传递store。
而最核心的功能在connect模块中。闭包
正如这个模块的命名,connect模块才是真正链接了React和Redux。
如今,咱们能够先回想一下Redux是怎样运做的:首先须要注册一个全局惟一的store对象,用来维护整个应用的state;当要变动state时,咱们会dispatch一个action,reducer根据action更新相应的state。
下面咱们再考虑一下使用react-redux时,咱们作了什么:app
import React from "react" import ReactDOM from "react-dom" import { bindActionCreators } from "redux" import {connect} from "react-redux" class xxxComponent extends React.Component{ constructor(props){ super(props) } componentDidMount(){ this.props.aActions.xxx1(); } render ( <div> {this.props.$$aProps} </div> ) } export default connect( state=>{ return { $$aProps:state.$$aProps, $$bProps:state.$$bProps, // ... } }, dispatch=>{ return { aActions:bindActionCreators(AActions,dispatch), bActions:bindActionCreators(BActions,dispatch), // ... } } )(xxxComponent)
经过以上代码,咱们能够概括出如下信息:框架
一、使用了react-redux后,咱们导出的对象再也不是原先定义的xxxComponent,而是经过connect包裹后的新React.Component对象。
connect执行后返回一个函数(wrapWithConnect),那么其内部势必造成了闭包。而wrapWithConnect执行后,必需要返回一个ReactComponent对象,才能保证原代码逻辑能够正常运行,而这个ReactComponent对象经过render原组件,造成对原组件的封装。
二、渲染页面须要store tree中的state片断,变动state须要dispatch一个action,而这两部分,都是从this.props
获取。故在咱们调用connect时,做为参数传入的state和action,便在connect内部进行合并,经过props的方式传递给包裹后的ReactComponent。
好了,以上只是咱们的猜想,下面看具体实现,完整代码请戳这里。
connect完整函数声明以下:
connect( mapStateToProps(state,ownProps)=>stateProps:Object, mapDispatchToProps(dispatch, ownProps)=>dispatchProps:Object, mergeProps(stateProps, dispatchProps, ownProps)=>props:Object, options:Object )=>( component )=>component
再来看下connect函数体结构,咱们摘取核心步骤进行描述
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) { // 参数处理 // ... return function wrapWithConnect(WrappedComponent) { class Connect extends Component { constructor(props, context) { super(props, context) this.store = props.store || context.store; const storeState = this.store.getState() this.state = { storeState } } // 周期方法及操做方法 // ... render(){ this.renderedElement = createElement(WrappedComponent, this.mergedProps //mearge stateProps, dispatchProps, props ) return this.renderedElement; } } return hoistStatics(Connect, WrappedComponent); } }
其实已经基本印证了咱们的猜想:
一、connect经过context获取Provider中的store,经过store.getState()获取整个store tree 上全部state。
二、connect模块的返回值wrapWithConnect为function。
三、wrapWithConnect返回一个ReactComponent对象Connect,Connect从新render外部传入的原组件WrappedComponent,并把connect中传入的mapStateToProps, mapDispatchToProps与组件上原有的props合并后,经过属性的方式传给WrappedComponent。
下面咱们结合代码进行分析一下每一个函数的意义。
mapStateToProps(state,props)
必须是一个函数。
参数state为store tree中全部state,参数props为经过组件Connect传入的props。
返回值表示须要merge进props中的state。
以上代码用来计算待merge的state,[104-105]经过调用finalMapStateToProps
获取merge state。其中做为参数的state经过store.getState()
获取,很明显是store tree中全部的state
mapDispatchToProps(dispatch, props)
能够是一个函数,也能够是一个对象。
参数dispatch为store.dispatch()
函数,参数props为经过组件Connect传入的props。
返回值表示须要merge进props中的action。
以上代码用来计算待merge的action,代码逻辑与计算state十分类似。做为参数的dispatch
就是store.dispatch
。
mergeProps
是一个函数,定义了mapState
,mapDispatch
及this.props
的合并规则,默认合并规则以下:
须要注意的是:若是三个对象中字段出现同名,前者会被后者覆盖
若是经过connect注册了mergeProps
方法,以上代码会使用mergeProps
定义的规则进行合并,mergeProps
合并后的结果,会经过props传入Connect组件。
options
是一个对象,包含pure
和withRef
两个属性
表示是否开启pure优化,默认值为true
withRef用来给包装在里面的组件一个ref,能够经过getWrappedInstance方法来获取这个ref,默认为false。
文章一开始咱们也提到React其实跟Redux没有直接联系,也就是说,Redux中dispatch触发store tree中state变化,并不会致使React从新渲染。
react-redux才是真正触发React从新渲染的模块,那么这一过程是怎样实现的呢?
刚刚提到,connect模块返回一个wrapWithConnect函数,wrapWithConnect函数中又返回了一个Connect组件。Connect组件的功能有如下两点:
一、包装原组件,将state和action经过props的方式传入到原组件内部
二、监听store tree变化,使其包装的原组件能够响应state变化
下面咱们主要分析下第二点:
Redux中,能够经过store.subscribe(listener)
注册一个监听器。listener会在store tree更新后执行。
以上代码为Connect组件内部,向store tree注册listener的过程。
[199] 调用store.subscribe
注册一个名为handleChange
的listener,返回值为当前listener的注销函数。
能够看到,当Connect组件加载到页面后,当前组件开始监听store tree变化。
当当前Connect组件销毁后,咱们但愿其中注册的listener也一并销毁,避免性能问题。此时能够在Connect的componentWillUnmount
周期函数中执行这一过程。
有了触发组件更新的时机,咱们下面主要看下,组件是经过何种方式触发从新渲染
[244-245] Connect组件在初始化时,就已经在this.state
中缓存了store tree中state的状态。这两行分别取出当前state状态和变动前state状态进行比较
[262] 比较过程暂时略过,这一行将最终store tree中state经过this.setState()
更新到Connect内部的state中,而this.setState()
方法正好能够触发Connect及其子组件的从新渲染。
能够看到,react-redux的核心功能都在connect模块中,理解好这个模块,有助于咱们更好的使用react-redux处理业务问题,优化代码性能。
本文经过分析react-redux源码,详细介绍了Provider和connect模块,从新梳理了Reat、redux、react-redux三者间的关系。 我的以为多看看源码仍是颇有好处的,一方面能够加深本身对已使用框架的理解;再一方面能够学到一些优秀的编程思路。 技术这条路上,懂的越多,不懂的也就越多,学无止境,戒骄戒躁。