注意:文章很长,只想了解逻辑而不深刻的,能够直接跳到 总结部分。
首先,从它暴露对外的API
开始前端
ReactReduxContext /* 提供了 React.createContext(null) */ Provider /* 一个储存数据的组件,渲染了ContextProvider,内部调用redux中store.subscribe 订阅数据,每当redux中的数据变更,比较新值与旧值,判断是否从新渲染 */ connect /* 一个高阶组件,第一阶传入对数据处理方法,第二阶传入要渲染的组件 内部处理了: 1. 对参数的检查 2. 对传入的数据处理方法进行处理 (没传怎么处理,传了提供什么参数,传的类型不一样怎么处理,结果如何比较等等) 3. 静态方法转移 4. 对渲染组件的传递(传递给connectAdvanced) */ connectAdvanced /* 保存每一次执行的数据,执行connect定义的方案和逻辑,新旧数据对比(全等对比),渲染组件 这里做为公开API,若是咱们去使用,那么connect里面的逻辑就须要咱们自定义了。 */
如今对它的大概工做范围有了解后,咱们能够开始沿着执行顺序分析。react
咱们使用时,当写完了redux的reducer
, action
, bindActionCreators
, combineReducers
, createStore
这一系列内容后,
咱们获得了一个store
git
会先使用<Provider store={store}
包裹住根组件。github
这时,Provider
组件开始工做redux
componentDidMount() { this._isMounted = true this.subscribe() }
第一次加载,须要执行subscribe
数组
subscribe
是什么呢,就是对redux
的store
执行subscribe
一个自定义函数,
这样,每当数据变更,这个函数便会执行闭包
subscribe() { const { store } = this.props // redux 的 store 订阅 // 订阅后,每当state改变 则自动执行这个函数 this.unsubscribe = store.subscribe(() => { // store.getState() 获取最新的 state const newStoreState = store.getState() // 组件未加载,取消 if (!this._isMounted) { return } // 比较state是否相等,全等的不更新 this.setState(providerState => { if (providerState.storeState === newStoreState) { return null } return { storeState: newStoreState } }) }) /* ... */ }
看到吗,这个自定义函数很是简单,每次收到数据,进行全等比较,不等则更新数据。app
这个组件的另2个生命周期函数:ide
componentWillUnmount() { if (this.unsubscribe) this.unsubscribe() this._isMounted = false } componentDidUpdate(prevProps) { // 比较store是否相等,若是相等则跳过 if (this.props.store !== prevProps.store) { // 取消订阅以前的,再订阅如今的(由于数据(store)不一样了) if (this.unsubscribe) this.unsubscribe() this.subscribe() } }
这2段的意思就是,每当数据变了,就取消上一次数据的订阅,在订阅本次的数据,
当要销毁组件,取消订阅。函数
一段题外话(可跳过):
这个逻辑用
Hooks
的useEffect
简直完美匹配!useEffect(()=>{ subscribe() return ()=>{ unSubscribe() } },props.data)这段的意思就是,当
props.data
发生改变,执行unSubscribe()
,再执行subscribe()
。逻辑彻底一致有没有!
最后的render
:
这里Context
就是React.createContext(null)
<Context.Provider value={this.state}> {this.props.children} </Context.Provider>
到这里我称为react-redux
的第一阶段。
一个小总结,第一阶段就作了1件事:
定义了Provider
组件,内部订阅了store
。
到主菜了,先看它的export
export default createConnect()
一看,咱们应该有个猜想,这货createConnect
是个高阶函数。
看看它的参数吧。
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {}) { /* ... */ }
题外话:一个编写默认对象内部含有默认值的方法
function a({x=1,y=2}={}){} a() // x:1,y:2 a({}) // x:1,y:2 a({x:2,z:5}) //x:2,y:2
这里先说明一下它的参数,后面读起来会很顺。
connectHOC: 一个重要组件,用于执行已肯定的逻辑,渲染最终组件,后面会详细说。 mapStateToPropsFactories: 对 mapStateToProps 这个传入的参数的类型选择一个合适的方法。 mapDispatchToPropsFactories: 对 mapDispatchToProps 这个传入的参数的类型选择一个合适的方法。 mergePropsFactories: 对 mergeProps 这个传入的参数的类型选择一个合适的方法。 selectorFactory: 以上3个只是简单的返回另外一个合适的处理方法,它则执行这些处理方法,而且对结果定义了如何比较的逻辑。
可能有点绕,但react-redux
就是这么一个个高阶函数组成的,selectorFactory
后面会详细说。
首先咱们再次肯定这3个名字很长,实际很简单的函数(源码这里不放了)
mapStateToPropsFactories
mapDispatchToPropsFactories
mergePropsFactories
它们只是判断了参数是否存在,是什么类型,而且返回一个合适的处理方法,它们并无任何处理逻辑。
const MyComponent=connect((state)=>state.articles})
这里我只定义了mapStateToProps
,而且是个function
,那么mapStateToPropsFactories
就会返回一个
处理function
的方法。
我没有定义mapDispatchToProps
,那么mapDispatchToPropsFactories
检测不到参数,
则会提供一个默认值dispatch => ({ dispatch })
,返回一个处理非function
(object)的方法。
那么处理逻辑是谁定义呢?
wrapMapToProps.js
这个文件内部作了如下事情:
object
的方法(简单的返回便可,由于最终目的就是要object)。函数
和高阶函数
(执行2次)的方法,这个方法比上面的复杂在于它须要检测参数是否订阅了ownProps
。检测方法很简单,就是检查参数的length
(这里dependsOnOwnProps
是上一次检查的结果,若是存在则不须要再次检查)
export function getDependsOnOwnProps(mapToProps) { return mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined ? Boolean(mapToProps.dependsOnOwnProps) : mapToProps.length !== 1 }
回到connect,继续往下看
export function createConnect({ /* 上面所讲的参数 */ } = {}) { return function connect( mapStateToProps, mapDispatchToProps, mergeProps, { pure = true, areStatesEqual = strictEqual, areOwnPropsEqual = shallowEqual, areStatePropsEqual = shallowEqual, areMergedPropsEqual = shallowEqual, ...extraOptions } = {} ) { /* ... */ } }
已经到了咱们传递参数的地方,前3个参数意思就不解释了,最后的参数options
areStatesEqual = strictEqual, // ===比较 areOwnPropsEqual = shallowEqual, // 浅比较 areStatePropsEqual = shallowEqual, // 浅比较 areMergedPropsEqual = shallowEqual, // 浅比较
它们用在selectorFactory
这个比较数据结果的方法内部。
继续往下看
export function createConnect({ /* 上面已讲 */ } = {}) { return function connect( /* 上面已讲 */ ) { const initMapStateToProps = match( mapStateToProps, mapStateToPropsFactories, 'mapStateToProps' ) const initMapDispatchToProps = match( mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps' ) const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
这里定义了3个变量(函数),match
的做用是什么?
以mapStateToProps
举例来讲,
由于上面也说了,mapStateToPropsFactories
里面有多个方法,须要找到一个适合mapStateToProps
的,match
就是干这事了。
match
方法内部遍历mapStateToPropsFactories
全部的处理方法,任何一个方法可以匹配参数mapStateToProps
,便被match
捕获返回,
若是一个都找不到则报错提示参数配置错误。
如今这3个变量定义明确了,都是对应的参数的合适的处理方法。
至此,咱们已经完成了第二阶段,
作个小总结,第二阶段作了哪些事:
connect
接收了对参数处理方案(3个...Factories
)。connect
接收了参数的结果比较方案(selectFactory
)connect
接收了参数(mapStateToProps
,mapDispatchToProps
,mergeProps
,options
)。are...Equal
,其实就是全等比较
和浅比较
)。前2个阶段都是定义阶段,接下来须要咱们传入自定义组件,也就是最后一个阶段
connect(...)(Component)
接着看connect
源码
export function createConnect({ /* 上面已讲 */ } = {}) { return function connect( /* 上面已讲 */ ) { /* 上面已讲 */ return connectHOC(selectorFactory, { // 方法名称,用在错误提示信息 methodName: 'connect', // 最终渲染的组件名称 getDisplayName: name => `Connect(${name})`, shouldHandleStateChanges: Boolean(mapStateToProps), // 如下是传递给 selectFactory initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, // any extra options args can override defaults of connect or connectAdvanced ...extraOptions }) } }
这里执行了connectHOC()
,传递了上面已经讲过的参数,而connectHOC = connectAdvanced
所以咱们进入最后一个对外API
,connectAdvanced
connectAdvanced
函数,以前也提过,就是一个执行、组件渲染和组件更新的地方。
它里面没有什么新概念,都是将咱们上面讲到的参数进行调用,最后根据结果进行渲染新组件。
仍是从源码开始
export default function connectAdvanced( selectorFactory, { // 执行后做用于connect这个HOC组件名称 getDisplayName = name => `ConnectAdvanced(${name})`, // 用于错误提示 methodName = 'connectAdvanced', // 有REMOVED标志,这里不关注 renderCountProp = undefined, // 肯定connect这个HOC是否订阅state变更,好像已经没有用到了 shouldHandleStateChanges = true, // 有REMOVED标志,这里不关注 storeKey = 'store', // 有REMOVED标志,这里不关注 withRef = false, // 是否经过 forwardRef 暴露出传入的Component的DOM forwardRef = false, // React的createContext context = ReactReduxContext, // 其他的(比较方法,参数处理方法等)将会传递给上面的 selectFactory ...connectOptions } = {} ) { /* ... */ }
参数也没什么特别的,有一个forwardRef
做用就是能获取到咱们传入的Component
的DOM。
这里也不深刻。
接着看
export default function connectAdvanced( /* 上面已讲 */ ) { /* ...对参数的一些验证和提示哪些参数已经做废... */ // 定义Context const Context = context return function wrapWithConnect(WrappedComponent) { /* ...检查 WrappedComponent 是否符合要求... */ /* ...获取传入的WrappedComponent的名称... */ /* ...经过WrappedComponent的名称计算出当前HOC的名称... */ /* ...获取一些上面的参数(没有新的参数,都是以前见过的)... */ // Component就是React.Component let OuterBaseComponent = Component let FinalWrappedComponent = WrappedComponent // 是否纯组件 if (pure) { OuterBaseComponent = PureComponent } /* 定义 makeDerivedPropsSelector 方法,做用后面讲 */ /* 定义 makeChildElementSelector 方法,做用后面讲 */ /* 定义 Connect 组件,做用后面讲 */ Connect.WrappedComponent = WrappedComponent Connect.displayName = displayName /* ...若是是forWardRef 为true的状况,此处不深刻... */ // 静态方法转换 return hoistStatics(Connect, WrappedComponent) } }
这一段特别长,所以我将不过重要的直接用注释说明了它们在作什么,具体代码就不放了(不重要)。
而且定义了3个新东西,makeDerivedPropsSelector
,makeChildElementSelector
,Connect
。
先看最后一句hoistStatics
就是hoist-non-react-statics
,它的做用是将组件WrappedComponent
的全部非React
静态方法传递到Connect
内部。
那么最终它仍是返回了一个Connect
组件。
这个组件已是咱们写了完整connect(...)(Component)
的返回值了,因此能肯定,只要调用<Connect />
,就能渲染出一个新的组件出来。
所以它的功能就是肯定是否重复更新组件和肯定到底更新什么?
看一个组件,从constructor
看起
class Connect extends OuterBaseComponent { constructor(props) { super(props) /* ...提示一些无用的参数...*/ this.selectDerivedProps = makeDerivedPropsSelector() this.selectChildElement = makeChildElementSelector() this.renderWrappedComponent = this.renderWrappedComponent.bind(this) } /* ... */ }
绑定了一个方法,看名字是render的意思,先无论它。
执行了2个函数。
Connect
组件还没完,这里先放着,咱们先看makeDerivedPropsSelector
和makeChildElementSelector
function makeDerivedPropsSelector() { // 闭包储存上一次的执行结果 let lastProps let lastState let lastDerivedProps let lastStore let sourceSelector return function selectDerivedProps(state, props, store) { // props和state都和以前相等 直接返回上一次的结果 if (pure && lastProps === props && lastState === state) { return lastDerivedProps } // 当前store和lastStore不等,更新lastStore if (store !== lastStore) { lastStore = store // 终于调用 selectorFactory 了 sourceSelector = selectorFactory( store.dispatch, selectorFactoryOptions ) } // 更新数据 lastProps = props lastState = state // 返回的就是最终的包含全部相应的 state 和 props 的结果 const nextProps = sourceSelector(state, props) // 最终的比较 if (lastDerivedProps === nextProps) { return lastDerivedProps } lastDerivedProps = nextProps return lastDerivedProps } }
大概的说,makeDerivedPropsSelector
的执行,先判断了当前传入的props(组件的props)
和state(redux传入的state)
跟之前的是否全等,若是全等就不须要更新了;
若是不等,则调用了高阶函数selectFactory
,而且得到最终数据,最后再判断最终数据和以前的最终数据是否全等。
为何第一次判断了,还要判断第二次,并且都是===
判断?
由于第一次获取的state
是redux
传入的,是整个APP的全部数据,它们不等说明有组件更新了,但不肯定是不是当前组件;
第二次比较的是当前组件的最新数据和之前数据对比。
如今,咱们知道selectFactory
的做用是获取当前组件的的最新数据,深刻源码看看。
export default function finalPropsSelectorFactory( // redux store的store.dispatch dispatch, // 3种已经肯定了的处理方法 { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options } ) { // 返回一个针对用户传入的类型的解析函数 // 例如 mapStateToProps 若是是function,那么就返回proxy,proxy能够判断是否须要ownProps,而且对高阶函数的 mapStateToProps 进行2次处理, // 最终确保返回一个plainObject,不然报错 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 // 默认pure问题true,所以执行 pureFinalPropsSelectorFactory(...) return selectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options ) }
参数就不说了,看注释。
如下3个,到底返回了什么,源码在wrapMapToProps.js
,上面也说过这个文件内部作了什么事情。
const mapStateToProps = initMapStateToProps(dispatch, options) const mapDispatchToProps = initMapDispatchToProps(dispatch, options) const mergeProps = initMergeProps(dispatch, options)
这3个调用返回的一个函数,名字叫proxy
,这个proxy
一旦调用,
就能返回通过mapStateToProps
, mapDispatchToProps
, mergeProps
这3个参数处理事后的数据(plainObject
)。
接下来:
const selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory // 默认pure问题true,所以执行 pureFinalPropsSelectorFactory(...) return selectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options )
返回了selectorFactory
的调用值,也就是pureFinalPropsSelectorFactory
(pure默认为true)。
看pureFinalPropsSelectorFactory
,它的代码很多,但逻辑很明了,大方向就是对比数据。
这里关键的如何比较不列代码,只用注释讲明白它的逻辑。
export function pureFinalPropsSelectorFactory( // 接受3个proxy方法 mapStateToProps, mapDispatchToProps, mergeProps, dispatch, // 接受3个比较方法 { areStatesEqual, areOwnPropsEqual, areStatePropsEqual } ) { /* ...定义变量保存以前的数据(闭包)... */ function handleFirstCall(firstState, firstOwnProps) { /* ...定义第一次执行数据比较的方法,也就是简单的赋值给上面定义的闭包变量... */ } function handleNewPropsAndNewState() { /* 当state和props都有变更时的处理方法 */ } function handleNewProps() { /* 当state无变更,props有变更时的处理方法 */ } function handleNewState() { /* 当state有变更,props无变更时的处理方法 */ } // 后续数据比较的方法 function handleSubsequentCalls(nextState, nextOwnProps) { // 浅比较 const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps) // 全等比较 const stateChanged = !areStatesEqual(nextState, state) // 更新数据 state = nextState ownProps = nextOwnProps // 当发生不相等的3种状况(关键) 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) } }
上面的闭包变量储存了上一次的数据,关键点就是当和这一次的数据比较后,若是处理更新。
react-redux
将它分为3种状况
state
和props
都相等。state
相等,props
不等。state
不等,props
相等。第一种:state
和props
都相等
不论是否订阅ownProps
,执行mapStateToProps
, 由于state
有变更。
只有订阅了ownProps
,才会执行mapDispatchToProps
,由于state
变更与mapDispatchToProps
无影响。
一定执行,将全部结果合并。
第二种:state
相等,props
不等
只有订阅了ownProps
,才会执行mapStateToProps
, 由于state
无变更。
只有订阅了ownProps
,才会执行mapDispatchToProps
,由于state
变更与mapDispatchToProps
无影响。
一定执行,将全部结果合并。
第三种:state
不等,props
相等
不论是否订阅ownProps
,执行mapStateToProps
, 由于state
有变更。
注意,这里结果须要浅比较
判断
由于若是没有浅比较
检查,而二者恰好浅比较相等
,
那么最后也会认为返回一个新的props,也就是至关于重复更新了。
之因此第一个state
和props
都有变更的不须要浅比较检查,
是由于若是props
变了,则必需要更新组件。
不会执行,由于它只关注props
。
只有上面浅比较不等,才会执行。
makeDerivedPropsSelector
的总结:
经过闭包管理数据,而且经过浅比较和全等比较判断是否须要更新组件数据。
makeChildElementSelector
也是一个高阶函数,储存了以前的数据
和组件
,而且判断与当前的判断。
这里是最终渲染组件的地方,由于须要判断一下刚才最终给出的数据是否须要去更新组件。
2个逻辑:
===
),更新组件。forWardRef
属性值与以前不等,更新组件。不然,返回旧组件(不更新)。
继续回到Connect
组件。
以后就是render
了
render() { // React的createContext const ContextToUse = this.props.context || Context return ( <ContextToUse.Consumer> {this.renderWrappedComponent} </ContextToUse.Consumer> ) }
Context.Consumer
内部必须是一个函数,这个函数的参数就是Context.Provider
的value
,也就是redux
的store
。
最后一个函数:renderWrappedComponent
renderWrappedComponent(value) { /* ...验证参数有效性... */ // 这里 storeState=store.getState() const { storeState, store } = value // 传入自定义组件的props let wrapperProps = this.props let forwardedRef if (forwardRef) { wrapperProps = this.props.wrapperProps forwardedRef = this.props.forwardedRef } // 上面已经讲了,返回最终数据 let derivedProps = this.selectDerivedProps( storeState, wrapperProps, store ) // 返回最终渲染的自定义组件 return this.selectChildElement(derivedProps, forwardedRef) }
总算结束了,可能有点混乱,作个总结吧。
我把react-redux
的执行流程分为3个阶段,分别对应咱们的代码编写(搭配导图阅读)
一张导图:
第一阶段:
对应的用户代码:
<Provider store={store}> <App /> </Provider>
执行内容有:
Provider
组件,这个组件内部订阅了redux
的store
,保证当store
发生变更,会马上执行更新。第二阶段:
对应的用户代码:
connect(mapStateToProps,mapDispatchToProps,mergeProps,options)
执行内容有:
connect
接收了参数(mapStateToProps
,mapDispatchToProps
,mergeProps
,options
)。connect
接收了对参数如何处理方案(3个...Factories
)。connect
接收了参数的结果比较方案(selectFactory
)are...Equal
,其实就是全等比较
和浅比较
)。第三阶段:
对应的用户代码:
let newComponent=connect(...)(Component) <newComponent />
执行内容有:
Component
)。Connect
组件。Component
的非React
静态方法转移到Connect
。Provider
传入的数据
(redux
的整个数据),利用闭包保存数据,用于和将来数据作比较。===
)有变更,执行上一阶段传入的参数,获取当前组件真正的数据。state
变更和props
变更的逻辑,判断返回新数据仍是旧数据。逻辑理顺了,仍是很好理解的。
其中第三阶段就是对外APIconnectAdvanced
的执行内容。
此处查看更多前端源码阅读内容。
或许哪一天,咱们须要设计一个专用的数据管理系统,那么就利用好connectAdvanced
,
咱们要作的就是编写一个自定义第二阶段
的逻辑体系。
感谢阅读!