React-redux源码总共分为两部分,第一是Provider
,第二是connect
。
Provider
Provider比较简单,主要代码如下:
class Provider extends Component { getChildContext() { return { [storeKey]: this[storeKey], [subscriptionKey]: null } } constructor(props, context) { super(props, context) this[storeKey] = props.store; } render() { return Children.only(this.props.children) } }复制代码
即通过context api将store传到子组件里面去。
connect
connect
算是一个比较有难度的部分,函数原型是:
connect(mapStateToProps,mapDispatchToProps,[mergeProps]) => (WrappedComponent) => HOC复制代码
我们尝试对其进行由上到下的分析:
入口
函数入口就是一个三阶函数:
export function createConnect({ connectHOC, mapStateToPropsFactories, mapDispatchTpropsFactories, mergePropsFactories, selectFactory}) => connect(mapStateToProps,mapDispatchToProps,mergeProps, { /*...*/}) => { const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps') const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps') const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps') return connectHOC(selectorFactory, { initMapStateToProps, initMapDispatchToProps, initMergeProps, //... })}export default createConnect()复制代码
写的其实比较绕,但是理解起来不难,首先createConnect
方法只相当于一个包装而已,将高阶组件connectHOC,以及一些工厂函数引入到connect
的父scope中来。
然后在connect
中,通过match函数来选择适当的factory来包装外界传入的参数)生成三个函数: initMapStateToProps
,initMapDispatchToProps
和initMergeProps
。
match函数的源码:
function match(arg, factories, name) { for (let i = factories.length - 1; i >= 0; i--) { const result = factories[i](arg) if (result) return result } return (dispatch, options) => { throw new Error(`Invalid value of type ${ typeof arg} for ${name} argument when connecting component ${options.wrappedComponentName}.`) }}复制代码
这段代码其中arg
就是connect函数接收的mapStateToProps
等,以倒序的方式寻找第一个返回非空的factory。我们可以先暂且不去深入推敲。
ConnectHOC
connectHOC
是整个react-redux的核心,在分析源代码之前,我们先看看一个工具函数:
function makeSelectorStateful(sourceSelector, store) { const selector = { run: function runComponentSelector(props) { try { const nextProps = sourceSelector(store.getState(), props) if (nextProps !== selector.props || selector.error) { selector.shouldComponentUpdate = true selector.props = nextProps selector.error = null } } catch (error) { selector.shouldComponentUpdate = true selector.error = error } } } return selector}复制代码
selector主要作用是对redux的state和dispatch与react的props进行合并(mergeProps
) 这段代码主要是对sourceSelector
进行封装,增加了错误处理,新旧值的比较等。
下面是connectHOC
的主要源码:
// ...export default function connectAdvanced( selectorFactory, // options object: { //省略掉的参数: withRef,displayName,renderCountProp,shouldHandleStateChanges,storeKey,getDisplayName // 传入selectorFactory中额外的参数 // 主要包括 `initMapStateToProps`,`initMapDispatchToProps`和`initMergeProps` ...connectOptions } = {}) { const subscriptionKey = storeKey + 'Subscription' // ... return function wrapWithConnect(WrappedComponent) { // ... const selectorFactoryOptions = { ...connectOptions, //... WrappedComponent } // 声明一个React组件 class Connect extends Component { constructor(props, context) { super(props, context) // ... // 从props或者context里面获得redux store this.store = props[storeKey] || context[storeKey] this.propsMode = Boolean(props[storeKey]) this.initSelector() this.initSubscription() } initSelector() { // 结合mapPropsTo...和mergeProps函数处理参数。(state, props) => nextProps const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions) // makeSelectorStateful: 包装sourceSelector函数,比较props和nextProps确定shouldComponentUpdate this.selector = makeSelectorStateful(sourceSelector, this.store) // 更新当前参数,实际上就是ownProps this.selector.run(this.props) } initSubscription() { // 如果不需要相应redux state变化,则不需要订阅 if (!shouldHandleStateChanges) return // 获取祖先传下来的订阅对象 const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey] // 生成当前组件的订阅对象,传入parentSub。 // 当parentSub不为空的时候,onStateChange实际上是注册到了parentSub的listeners数组,由组件手动调用notifyNestedSubs执行。 // 否则直接使用store.subscribe注册到redux中,由redux控制执行 // 这样做能始终保证祖先节点的更新早于子孙节点 this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this)) this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription) } onStateChange() { // 更新props this.selector.run(this.props) if (!this.selector.shouldComponentUpdate) { // 如果当前组件不需要更新,那么直接更新子组件 this.notifyNestedSubs() } else { // 先更新当前组件,再更新子组件 this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate // dummyState 为空,意味触发react的更新机制 this.setState(dummyState) } } getChildContext() { // 如果store是从props中获得,那么直接将祖先的subscription对象传下去。(仅作为一个传话人,不响应父组件的dispatch事件) // 否则,将自身的subscription传下去,形状如下 // Component A (B's subscription's parentSub == A) // | // -> Component B (C's subscription's parentSub == B) // | // -> Component C (E's subscription's parentSub == C) // | // |== Component D (propsMode === true) directly transfer C's sub context to E // | // -> Component E // when a dispatch occured in Component A,the callback exec order will be A -> B -> C -> E const subscription = this.propsMode ? null : this.subscription return { [subscriptionKey]: subscription || this.context[subscriptionKey] } } componentDidMount() { if (!shouldHandleStateChanges) return // 因为componentWillMount在ssr中执行,而componentDidMount和componentWillUnmount不执行。因此如果将trySubscribe方法放在componentWillMount中的话,在ssr中可能会因为没有调用componentWillUnmount的unsubscribe而内存泄漏 this.subscription.trySubscribe() // run即update,更新selector.nextProps,selector.shouldComponentUpdate this.selector.run(this.props) // 如果子组件在componentWillMount的时候dispatch了一个action,因而没有被上文的subscription捕获到,此时的selector就会检测出参数的不一致(shouldComponentUpdate = true),所以需要强制更新 if (this.selector.shouldComponentUpdate) this.forceUpdate() } // 更新props componentWillReceiveProps(nextProps) { this.selector.run(nextProps) } shouldComponentUpdate() { return this.selector.shouldComponentUpdate } componentWillUnmount() { if (this.subscription) this.subscription.tryUnsubscribe() // 为什么直接使用subscription.notifyNestedSubs而要拷贝出来分别处理: // 防止在notify过程中subscription为null,而null.notifyNestedSubs报错 this.subscription = null this.notifyNestedSubs = noop this.store = null this.selector.run = noop this.selector.shouldComponentUpdate = false } notifyNestedSubsOnComponentDidUpdate() { // 避免多次调用 this.componentDidUpdate = undefined this.notifyNestedSubs() } isSubscribed() { return Boolean(this.subscription) && this.subscription.isSubscribed() } addExtraProps(props) { // 如果没有额外的参数,直接返回props if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props // 防御性拷贝 const withExtras = { ...props } if (withRef) withExtras.ref = this.setWrappedInstance if (renderCountProp) withExtras[renderCountProp] = this.renderCount++ if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription return withExtras } render() { const selector = this.selector selector.shouldComponentUpdate = false if (selector.error) { throw selector.error } else { return createElement(WrappedComponent, this.addExtraProps(selector.props)) } } } // ... // 仅在开发环境下支持的的热重载 if (process.env.NODE_ENV !== 'production') { Connect.prototype.componentWillUpdate = function componentWillUpdate() { // We are hot reloading! if (this.version !== version) { this.version = version this.initSelector() let oldListeners = []; if (this.subscription) { oldListeners = this.subscription.listeners.get() this.subscription.tryUnsubscribe() } this.initSubscription() if (shouldHandleStateChanges) { this.subscription.trySubscribe() oldListeners.forEach(listener => this.subscription.listeners.subscribe(listener)) } } } } // 将WrappedComponent中的非React特定的静态属性(例如propTypes就是React的特定静态属性)赋值到Connect。 // 作用有点类似于Object.assign,但是仅复制非React特定的静态属性。 return hoistStatics(Connect, WrappedComponent) }}复制代码
至此,大部分逻辑我们已经很清楚了。现在我们来分析一下各种factory所做的事情:
selectorFactory
首先看主体:
export default function finalPropsSelectorFactory(dispatch, { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options}) { const mapStateToProps = initMapStateToProps(dispatch, options) const mapDispatchToProps = initMapDispatchToProps(dispatch, options) const mergeProps = initMergeProps(dispatch, options) if (process.env.NODE_ENV !== 'production') { verifySubselectors(mapStateToProps, mapDispatchToProps, mergeProps, options.displayName) } const selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory return selectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options )}复制代码
该函数相当于一个控制器,根据pure
选项来决定使用那种具体实现。 如果pure
为true
,则selectorFactory将会返回一个可以记忆结果的selector,这样才有可能使得connectHOC
里面的selector.shouldComponentUpdate
为false
。反之,selector将永远返回新的对象,selector.shouldComponentUpdate
始终为true
impureFinalPropsSelectorFactory
export function impureFinalPropsSelectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch) { return function impureFinalPropsSelector(state, ownProps) { return mergeProps( mapStateToProps(state, ownProps), mapDispatchToProps(dispatch, ownProps), ownProps ) }}复制代码
直接就很粗暴了
pureFinalPropsSelectorFactory
export function pureFinalPropsSelectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, { areStatesEqual, areOwnPropsEqual, areStatePropsEqual }) {// 闭包,memory之前的结果,仅当有变化的时候才更新 let hasRunAtLeastOnce = false let state let ownProps let stateProps let dispatchProps // 合并的结果 let mergedProps // 第一次调用 function handleFirstCall(firstState, firstOwnProps) { state = firstState ownProps = firstOwnProps stateProps = mapStateToProps(state, ownProps) dispatchProps = mapDispatchToProps(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) hasRunAtLeastOnce = true return mergedProps } function handleNewPropsAndNewState() { // state 已经 changed stateProps = mapStateToProps(state, ownProps) if (mapDispatchToProps.dependsOnOwnProps) dispatchProps = mapDispatchToProps(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleNewProps() { // 仅当state 用到了OwnProps才更新 if (mapStateToProps.dependsOnOwnProps) stateProps = mapStateToProps(state, ownProps) // 仅当disaptch 用到了OwnProps才更新 if (mapDispatchToProps.dependsOnOwnProps) dispatchProps = mapDispatchToProps(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } function handleNewState() { const nextStateProps = mapStateToProps(state, ownProps) // shallow compare state是否改变 const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps) stateProps = nextStateProps if (statePropsChanged) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) return mergedProps } // 非首次调用 function handleSubsequentCalls(nextState, nextOwnProps) { const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps) const stateChanged = !areStatesEqual(nextState, state) state = nextState ownProps = nextOwnProps if (propsChanged && stateChanged) return handleNewPropsAndNewState() if (propsChanged) return handleNewProps() if (stateChanged) return handleNewState() return mergedProps } return function pureFinalPropsSelector(nextState, nextOwnProps) { return hasRunAtLeastOnce ? handleSubsequentCalls(nextState, nextOwnProps) : handleFirstCall(nextState, nextOwnProps) }}复制代码
看上去复杂许多,但主要还是因为需要区分是否是首次调用而已。
mapStateToProps
mapStateToProps
和mapDispatchToProps
差不多,大部分都是公共代码(wrapMapToProps
),因此我们以mapStateToProps
为例:
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'export function whenMapStateToPropsIsFunction(mapStateToProps) { return (typeof mapStateToProps === 'function') ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps') : undefined}export function whenMapStateToPropsIsMissing(mapStateToProps) { return (!mapStateToProps) ? wrapMapToPropsConstant(() => ({})) : undefined}export default [ whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]复制代码
跟selectorFactory一样,这里同样有多种解决方案,不过不同的是,这里是直接将所有方案以数组的方式返回,供前面connect
中的match
函数匹配。
然后我们主要来看一下其中涉及到的几个函数:
wrapMapToPropsConstant
export function wrapMapToPropsConstant(getConstant) { return function initConstantSelector(dispatch, options) { const constant = getConstant(dispatch, options) function constantSelector() { return constant } constantSelector.dependsOnOwnProps = false return constantSelector }}复制代码
顾名思义,constant不允许结果发生变化的,所以函数里面调用传入的getConstant
函数之后,直接返回一个getter函数:contantSelector
wrapMapToPropsFunc
// methodName只用来debugexport function wrapMapToPropsFunc(mapToProps, methodName) { // options 只需要 displayname , 也只用来debug return function initProxySelector(dispatch, { displayName }) { const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) { return proxy.dependsOnOwnProps ? proxy.mapToProps(stateOrDispatch, ownProps) : proxy.mapToProps(stateOrDispatch) } proxy.dependsOnOwnProps = true proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) { proxy.mapToProps = mapToProps proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps) let props = proxy(stateOrDispatch, ownProps) // 如果mapToProps执行后返回的结果还是一个函数,则继续递归调用,直到它扁平为止 if (typeof props === 'function') { proxy.mapToProps = props proxy.dependsOnOwnProps = getDependsOnOwnProps(props) props = proxy(stateOrDispatch, ownProps) } if (process.env.NODE_ENV !== 'production') verifyPlainObject(props, displayName, methodName) return props } return proxy }}复制代码
这里主要运用了代理的方法,挺巧妙的对mapToProps
方法进行了包装,增加了dependsOnOwnProps
这个静态成员,判断方法很简单,直接判断Function的参数长度即可:
getDependsOnOwnProps
export function getDependsOnOwnProps(mapToProps) { return (mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined) ? Boolean(mapToProps.dependsOnOwnProps) : mapToProps.length !== 1}复制代码
至此,所有代码都通了, cheers!~