connect
今天看了下react-redux的源码,主要来看下connect的方法, 当前版本5.1.0
首先找到connect的入口文件。在src/index.js下找到。对应connect文件夹下的connect.js文件。
大致说下源码
connect流程connect.js对外暴露是通过export default createConnect(), 我们来看createConnect方法。 在这里,他是在对外暴露的时候直接运行,导致对外导出的结果就是他返回的一个connect方法. 然后再connect里他是返回了一个函数的结果。这个就对应到createHoc 所对应的connectAdvanced部分,然后再返回处理过的我们的容器组件.具体细节 看下面部分的源码:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {}) { return function connect( mapStateToProps, mapDispatchToProps, mergeProps, { pure = true, areStatesEqual = strictEqual, areOwnPropsEqual = shallowEqual, areStatePropsEqual = shallowEqual, areMergedPropsEqual = shallowEqual, ...extraOptions } = {} ) { const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps') const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps') const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps') return connectHOC(selectorFactory, { // used in error messages methodName: 'connect', // used to compute Connect's displayName from the wrapped component's displayName. getDisplayName: name => `Connect(${name})`, // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes shouldHandleStateChanges: Boolean(mapStateToProps), // passed through to selectorFactory initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, // any extra options args can override defaults of connect or connectAdvanced ...extraOptions }) } }我们知道,在容器组件平时对外暴露的时候是
```js const mapStateToProps = (state, ownProps) => ({ // ... })
const mapDispatchToProps = dispatch => ({ actions: bindActionCreators(Object.assign({}, actions), dispatch) })
export default connect( mapStateToProps, mapDispatchToProps )(YourComponent)
我们可以看到基本都是都是传递两个参数,`mapStateToProps&mapDispatchToProps`, 第三个参数`mergeProps `并没有传递,这个参数是干什么的?
他在
```js
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')这里初始化mergeProps,通过mergePropsFactories来处理里返回的两个函数来处理。 对应了两个函数对应了mergeProps的两种情况:
whenMergePropsIsFunction存在mergeProps并且是function的时候,会进行处理并返回一个函数, 这个函数的第一个参数是一个dispatch,第二个参数是一个对象,并且对象接收三个属性,如果不是一个方法,那就返回undefined.whenMergePropsIsOmitted方法会判断mergeProps是不是存在,如果存在就返回undefined,否则就会返回一个默认的mergeProps,可以发现,是在函数里调用的默认的mergeProps.
mapStateToProps&mapDispatchToProps和mergeProps的初始化类似,都会进行判断再去操作。
他们的初始化会返回一个函数:
initMapStateToProps & initMapDispatchToProps
这两个实际上是
wrapMapToProps.js中的wrapMapToPropsFunc函数返回的一个initProxySelector函数.initMergeProps
如果是空
mergeProps参数就使用默认的,如果mergeProps参数是一个函数,那就使用mergeProps.js的wrapMergePropsFunc并且返回initMergePropsProxy函数。
然后connect后面的第四个参数是一个对象,他接收一些属性。
然后就是下一步了,在使用的时候,我们connect()之后就是继续调用,传参是我们的容器组件。那这时,在源码里就是对应的return connectHOC(selectorFactory, {...}), 这个返回的是一个函数运行的结果,所以我们传参的容器组件这个参数,并不是这里的selectorFactory,而是他运行完成之后的结果所接受的参数。
上面有提到createHOC对应的是connectAdvanced.然后可以看到createHOC传参的第一个是selectorFactory, 就是对应的defaultSelectorFactory,然后第二个参数是一些对象。
接下来我们就可以看看对应createHOC的connectAdvanced方法了。
首先看connectAdvanced在参数方面,第一个保持不变,第二个进行了扩展,添加了几个属性。 我们还是从对外暴露的接口来看,他直接暴露的是connectAdvanced方法。因为我们在createHOC所需要的是一个结果, 所以我们通过源码看看他是怎么运行的.
在这个方法里,有返回一个方法wrapWithConnect(WrappedComponent){},好家伙,到这里我们可以知道connect里的return createHOC(...)的结果返回 的就是这个,然后再看返回的这个函数名称,很见名知意用connect包裹的函数的参数是包裹的组件.那就看看这个函数里面是啥。
一些基本的处理,然后就是一个connect基于React.Component的继承,最后是返回return hoistStatics(Connect, WrappedComponent), 这个hoistStatics是一个类似Object.assign的copy非react的static property. 换句话说,就是把传递的容器组件自定义的静态属性附加到connect组件上去。
我们再看看这个connect class, 他在构造函数里直接运行了this.initSelector()和this.initSubscription()我们先看下initSelector:
initSelector() {
// 这里的sourceSelector 就是`selectFactory`里的返回的闭包(pureFinalPropsSelector),
// 在运行返回闭包之前已经处理了`initMapStateToProps`,并且那个地方返回的是`proxy`, 有标记
// selectorFactory会在返回pureFinalPropsSelector函数(handleSubsequentCalls函数或者handleFirstCall函数)
const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
//返回selector对象,包含一个run函数
this.selector = makeSelectorStateful(sourceSelector, this.store)
this.selector.run(this.props)
}别急,我们慢慢的看下去。发现就是使用的selectorFactory方法来处理的, 我们就看看这个方法是怎么处理的。对外暴露的方法在这里, 他会根据pure属性来确定到底该如何处理。其实我们从这一段注释就可以知道了:
// If pure is true, the selector returned by selectorFactory will memoize its results,
// allowing connectAdvanced's shouldComponentUpdate to return false if final
// props have not changed. If false, the selector will always return a new
// object and shouldComponentUpdate will always return true.就是去处理到底是否应该重新渲染,所有如果你想要刷新的情况,可以在connect的传参的第四个对象里改变pure为false.
假设这个是一个pure,那么就会继续调用pureFinalPropsSelectorFactory。看这个参数:
// 下面的这个调用是在闭包的返回基础上调用,见`connect.js`的`createConnect`方法里
// 下面的这个initMapStateToProps是相当于运行的闭包,即`wrapMapToProps`里的`initProxySelector`.
// 但是这几乎等于没有运行,因为这里直接返回了proxy函数.
const mapStateToProps = initMapStateToProps(dispatch, options) // return proxy
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
const selectorFactory = options.pure
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory
// 上面返回了mapToProps之后,实则是返回了proxy.
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
)好的,我们再看pureFinalPropsSelectorFactory函数:
return function pureFinalPropsSelector(nextState, nextOwnProps) {
return hasRunAtLeastOnce
? handleSubsequentCalls(nextState, nextOwnProps)
: handleFirstCall(nextState, nextOwnProps)
}这个函数太长,我们看他返回的。那么我们直接在看看他调用的handleFirstCall函数:
function handleFirstCall(firstState, firstOwnProps) {
state = firstState
ownProps = firstOwnProps
// 这里实际上是在调用`wrapMapToProps -> wrapMapToPropsFunc`返回的`proxy`函数
//调用proxy函数会返回props,最后把这些props给merged并返回。
stateProps = mapStateToProps(state, ownProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
hasRunAtLeastOnce = true
return mergedProps
}好吧,这里看出来了吗?mergedProps,这又是啥,我们来看看上面说到的proxy函数:
export function wrapMapToPropsFunc(mapToProps, methodName) {
return function initProxySelector(dispatch, { displayName }) {
const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
return proxy.dependsOnOwnProps
? proxy.mapToProps(stateOrDispatch, ownProps)
: proxy.mapToProps(stateOrDispatch)
}
// allow detectFactoryAndVerify to get ownProps
proxy.dependsOnOwnProps = true
proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
//这里是重点,把mapToProps指向`connect.js`里传递进来的`mapXxToProps`,也就是我们组建写的:
// const mapStateToProps = (state, ownProps) => ({myNeedProps: state.yourState})
// 不重写会死循环
proxy.mapToProps = mapToProps
proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
let props = proxy(stateOrDispatch, ownProps)
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
}
}可以看到吧,proxy会给你返回props。
然后initSelector同时也会为当前对象创建一个selector: 我们结合上面的initSelector可以知道,sourceSelector就是selectorFactory返回的handleSubsequentCalls函数或者handleFirstCall函数:
function makeSelectorStateful(sourceSelector, store) {
// wrap the selector in an object that tracks its results between runs.
const selector = {
// props 就是this.props,然后会调用sourceSelector进行mergeProps
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
}最后来run,如果符合条件或者出错,便会改变shouldComponentUpdate = true,去重新 render.
再来看看initSubscription方法:
initSubscription() {
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)
}先判断shouldHandleStateChanges是不是成立,如果成立则进行,否则返回。成立会进行初始化,正如其名,初始化订阅相关事件。
这里初始化结束之后,我们看下componentDidMount方法,
componentDidMount() {
if (!shouldHandleStateChanges) return
// componentWillMount fires during server side rendering, but componentDidMount and
// componentWillUnmount do not. Because of this, trySubscribe happens during ...didMount.
// Otherwise, unsubscription would never take place during SSR, causing a memory leak.
// To handle the case where a child component may have triggered a state change by
// dispatching an action in its componentWillMount, we have to re-run the select and maybe
// re-render.
this.subscription.trySubscribe()
this.selector.run(this.props)
if (this.selector.shouldComponentUpdate) this.forceUpdate()
}他也会和initSubscription一样的去判断,确定是否进行下去。下面会进行尝试订阅. 然后去运行selector,运行selector之后,会判断shouldComponentUpdate,如果成立,则会进行forceUpdate, 注意: forceUpdate会跳过shouldComponentUpdate的判断.
最后看下render方法,
render() {
const selector = this.selector
selector.shouldComponentUpdate = false
if (selector.error) {
throw selector.error
} else {
return createElement(WrappedComponent, this.addExtraProps(selector.props))
}
}可以看到.他会对属性进行重写成false,如果报错,就跑出错误,如果正常,那就进行render创建元素到页面。
总结
provider 这个主要就是为了提供一个上下文的
store,使得下级的组件不需要props可以直接获取store。mapStateToProps&mapDispatchToProps
这两个就是把对象放在props里。 主要的操作就是通过wrapMapToProps.js中的wrapMapToPropsFunc函数去处理,在闭包proxy中代理我们传入的mapToProps方法去返回合并的props。 这个调用的proxy函数的是在connectAdvanced.js的makeSelectorStateful函数中,会传入一个store.getState()。至于dispatch则在initSelector初始化的时候就传递进去了。
mergeProps就是把这些props给合并起来。
没有使用
setState,是如何改变状态进行渲染的他会对
connectAdvanced.js的onStateChange进行订阅,进行selector.run去对比前后的props。因为我们知道redux的dispatch代码里有执行 订阅相关的操作。 这样每次dispatch之后都会去判断shouldComponentUpdate,如果是就会去this.setState(dummyState)去render。
Last updated