react-redux源码解读 上一篇翻了一下redux的源码 这一篇就研究下react-redux吧 原理大家都知道 就是利用react的Context Api 那我们看看它是怎么在react应用中施展魔法的吧~~
看代码 先看index.js
src/index.js 这个文件是主入口 暴露出 Provider/connectAdvanced/ReactReduxContext/connect四个核心方法
其中,Provider是个react组件 是存储store数的根节点
ReactReduxContext是个react context,所有子组件的都要从这个context获取store的数据
connect 是关联组件和store的方法
connectAdvanced 是一个高阶组件 负责将connect的参数进行封装添加到子组件的props上
harmony 1 2 3 4 5 6 import Provider from './components/Provider' import connectAdvanced from './components/connectAdvanced' import { ReactReduxContext } from './components/Context' import connect from './connect/connect' export { Provider , connectAdvanced, ReactReduxContext , connect }
看到这几个方法就能猜到react-redux到底做了什么?难点在哪里?怎么避免不必要的渲染?以及使用高阶函数时对于ref的转发?
我们不妨先看Provider这个组件吧 毕竟他也是react使用redux的入口 也是关联store的第一环
src/components/Provider.js Provider是一个react组件 接收三个props
store
context
children 由此我们猜测 react-redux内部肯定会使用相当多的闭包进行缓存以避免不必要的渲染 结合connect的使用 推断会有根据storeState和ownProps进行判断是否进行渲染的逻辑 也理应会有
harmony 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 class Provider extends Component { constructor (props ) { super (props) const { store } = props this .state = { storeState : store.getState (), store } } componentDidMount ( ) { this ._isMounted = true this .subscribe () } componentWillUnmount ( ) { if (this .unsubscribe ) this .unsubscribe () this ._isMounted = false } componentDidUpdate (prevProps ) { if (this .props .store !== prevProps.store ) { if (this .unsubscribe ) this .unsubscribe () this .subscribe () } } subscribe ( ) { const { store } = this .props this .unsubscribe = store.subscribe (() => { const newStoreState = store.getState () if (!this ._isMounted ) { return } this .setState (providerState => { if (providerState.storeState === newStoreState) { return null } return { storeState : newStoreState } }) }) const postMountStoreState = store.getState () if (postMountStoreState !== this .state .storeState ) { this .setState ({ storeState : postMountStoreState }) } } render ( ) { const Context = this .props .context || ReactReduxContext return ( <Context.Provider value ={this.state} > {this.props.children} </Context.Provider > ) } }
src/connect/connect.js harmony 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 export function createConnect ({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {} ) { return function connect ( mapStateToProps, mapDispatchToProps, mergeProps, { // 是否启用PureComponent模式 pure = true , // 在调用selectorFactory的时候 为了使用缓存需要调用的比对方法 areStatesEqual = strictEqual, // 同上 areOwnPropsEqual = shallowEqual, // 同上 areStatePropsEqual = shallowEqual, // 同上 areMergedPropsEqual = shallowEqual, // 别的参数 getDisplayName/methodName/renderCountProp/shouldHandleStateChanges等... ...extraOptions } = {} ) { const initMapStateToProps = match ( mapStateToProps, mapStateToPropsFactories, 'mapStateToProps' ) const initMapDispatchToProps = match ( mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps' ) const initMergeProps = match (mergeProps, mergePropsFactories, 'mergeProps' ) return connectHOC (selectorFactory, { methodName : 'connect' , getDisplayName : name => `Connect(${name} )` , shouldHandleStateChanges : Boolean (mapStateToProps), initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, ...extraOptions }) } } export default createConnect ()
这个createConnect方法接收5个参数
connectHoc 默认值为一个高阶函数
mapStateToPropsFactories 为用户传入的mapStateToProps值准备的类型映射 每个类型对应不同的执行方法
mapDispatchToPropsFactories 同上
mergePropsFactories 同上
selectorFactory 这个是通过mapStateToProps和mapDispatchToProps获取最终stateProps, dispatchProps并将它们merge到组件props的过程
match方法 match方法会依次执行第二个参数的每个元素 入参为第一个参数 只要有返回值 就return 否则抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 } .` ) } }
我们看一下initMapStateToProps是怎么产生的
1 2 3 4 5 const initMapStateToProps = match ( mapStateToProps, mapStateToPropsFactories, 'mapStateToProps' )
match的第一个参数为用户传入的mapStateToProps 通常是这样的
1 2 3 const mapStateToProps = state => ({ username : state.profile .username , })
第二个参数为mapStateToPropsFactories 默认值为defaultMapStateToPropsFactories 我们看看他是何方神圣
src/connect/mapStateToProps.js 1 2 3 4 5 6 7 8 9 10 11 12 13 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]
从这个文件可以知道defaultMapStateToPropsFactories就是暴露出来的这个数组 上面我们提到过 这个东西是干嘛用的呢 这是个参数类型映射集合 从名字可以看出mapStateToProps可以是function 也可以为空
我们上面举了个function的🌰 先看是function的情况吧
1 2 3 4 5 function whenMapStateToPropsIsFunction (mapStateToProps ) { return typeof mapStateToProps === 'function' ? wrapMapToPropsFunc (mapStateToProps, 'mapStateToProps' ) : undefined }
这个方法判断如果mapStateToProps是function类型的就返回wrapMapToPropsFunc(mapStateToProps, ‘mapStateToProps’)的执行结果 我们移步看看wrapMapToPropsFunc是何方神圣吧
src/connect/wrapMapToProps.js 1 2 3 4 5 export function wrapMapToPropsFunc (mapToProps, methodName ) { return function initProxySelector (dispatch, { displayName } ) { } }
第一次看这个代码有点懵逼,这是个啥,又没有什么上下文之类的 这个不用管 只需要知道返回的是一个(dispatch, options) => {} 这样的initProxySelector函数就行了 当mapStateToProps传空的时候也是类似这种情况 代码就不贴了 下面我们会回顾这个代码的
src/components/connectAdvanced.js harmony 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 export default function connectAdvanced ( selectorFactory, { getDisplayName = name => `ConnectAdvanced(${name} )` , methodName = 'connectAdvanced' , renderCountProp = undefined , shouldHandleStateChanges = true , storeKey = 'store' , withRef = false , forwardRef = false , context = ReactReduxContext, ...connectOptions } = {} ) { invariant ( renderCountProp === undefined , `renderCountProp is removed. render counting is built into the latest React dev tools profiling extension` ) invariant ( !withRef, 'withRef is removed. To access the wrapped instance, use a ref on the connected component' ) const customStoreWarningMessage = 'To use a custom Redux store for specific components, create a custom React context with ' + "React.createContext(), and pass the context object to React Redux's Provider and specific components" + ' like: <Provider context={MyContext}><ConnectedComponent context={MyContext} /></Provider>. ' + 'You may also pass a {context : MyContext} option to connect' invariant ( storeKey === 'store' , 'storeKey has been removed and does not do anything. ' + customStoreWarningMessage ) const Context = context return function wrapWithConnect (WrappedComponent ) { if (process.env .NODE_ENV !== 'production' ) { invariant ( isValidElementType (WrappedComponent ), `You must pass a component to the function returned by ` + `${methodName} . Instead received ${stringifyComponent( WrappedComponent )} ` ) } const wrappedComponentName = WrappedComponent .displayName || WrappedComponent .name || 'Component' const displayName = getDisplayName (wrappedComponentName) const selectorFactoryOptions = { ...connectOptions, getDisplayName, methodName, renderCountProp, shouldHandleStateChanges, storeKey, displayName, wrappedComponentName, WrappedComponent } const { pure } = connectOptions let OuterBaseComponent = Component if (pure) { OuterBaseComponent = PureComponent } class Connect extends OuterBaseComponent {} Connect .WrappedComponent = WrappedComponent Connect .displayName = displayName if (forwardRef) { const forwarded = React .forwardRef (function forwardConnectRef ( props, ref ) { return <Connect wrapperProps ={props} forwardedRef ={ref} /> }) forwarded.displayName = displayName forwarded.WrappedComponent = WrappedComponent return hoistStatics (forwarded, WrappedComponent ) } return hoistStatics (Connect , WrappedComponent ) } }
这段代码的作用主要是就是返回一个新的组件 重点看 Connect组件
harmony 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 class Connect extends OuterBaseComponent { constructor (props ) { super (props) invariant ( forwardRef ? !props.wrapperProps [storeKey] : !props[storeKey], 'Passing redux store in props has been removed and does not do anything. ' + customStoreWarningMessage ) this .selectDerivedProps = makeDerivedPropsSelector () this .selectChildElement = makeChildElementSelector () this .indirectRenderWrappedComponent = this .indirectRenderWrappedComponent .bind ( this ) } indirectRenderWrappedComponent (value ) { return this .renderWrappedComponent (value) } renderWrappedComponent (value ) { invariant ( value, `Could not find "store" in the context of ` + `"${displayName} ". Either wrap the root component in a <Provider>, ` + `or pass a custom React context provider to <Provider> and the corresponding ` + `React context consumer to ${displayName} in connect options.` ) const { storeState, store } = value let wrapperProps = this .props let forwardedRef if (forwardRef) { wrapperProps = this .props .wrapperProps forwardedRef = this .props .forwardedRef } let derivedProps = this .selectDerivedProps ( storeState, wrapperProps, store, selectorFactoryOptions ) return this .selectChildElement ( WrappedComponent , derivedProps, forwardedRef ) } render ( ) { const ContextToUse = this .props .context && this .props .context .Consumer && isContextConsumer (<this.props.context.Consumer /> ) ? this .props .context : Context return ( <ContextToUse.Consumer > {this.indirectRenderWrappedComponent} </ContextToUse.Consumer > ) } }
先从构造函数看起
1 2 this .selectDerivedProps = makeDerivedPropsSelector ()this .selectChildElement = makeChildElementSelector ()
makeDerivedPropsSelector、makeChildElementSelector方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 function makeDerivedPropsSelector ( ) { let lastProps let lastState let lastDerivedProps let lastStore let lastSelectorFactoryOptions let sourceSelector return function selectDerivedProps ( state, props, store, selectorFactoryOptions ) { if (pure && lastProps === props && lastState === state) { return lastDerivedProps } if ( store !== lastStore || lastSelectorFactoryOptions !== selectorFactoryOptions ) { lastStore = store lastSelectorFactoryOptions = selectorFactoryOptions sourceSelector = selectorFactory ( store.dispatch , selectorFactoryOptions ) } lastProps = props lastState = state const nextProps = sourceSelector (state, props) lastDerivedProps = nextProps return lastDerivedProps } } function makeChildElementSelector ( ) { let lastChildProps, lastForwardRef, lastChildElement, lastComponent return function selectChildElement ( WrappedComponent, childProps, forwardRef ) { if ( childProps !== lastChildProps || forwardRef !== lastForwardRef || lastComponent !== WrappedComponent ) { lastChildProps = childProps lastForwardRef = forwardRef lastComponent = WrappedComponent lastChildElement = ( <WrappedComponent {...childProps } ref ={forwardRef} /> ) } return lastChildElement } }
makeDerivedPropsSelector 这个方法这就要是给组件接收从store衍生的props做了一层缓存 细节可以自己去看 这边主要看一下用到的selectorFactory方法 这个方法是connectAdvanced的入参 回到src/connect/connect.js 这个selectorFactory有一个默认值为defaultSelectorFactory 我们看一下src/connect/selectorFactory.js
src/connect/selectorFactory.js 接收dispatch和options作为参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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 ) }
initProxySelector方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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) 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 }
回到Connect的构造函数 还有一个makeChildElementSelector方法 这个方法是的作用是对组件进行缓存 判断组件props ref和wrapperComponent返回新旧组件
以上就是简要的介绍了react-redux的源码 后续还会有补充…