react-redux 版本号 7.2.3
react-redux 依赖的库:html
"dependencies": { "@babel/runtime": "^7.12.1", "@types/react-redux": "^7.1.16", "hoist-non-react-statics": "^3.3.2", "loose-envify": "^1.4.0", "prop-types": "^15.7.2", "react-is": "^16.13.1" }
这里我直接把 react-redux 的源码下载了下来,因此这些依赖就必须手动安装了react
注意: 关于 hooks 的解析会放到下一文git
redux 是一个库,但更是一种思想, 而 react-redux 就是一座桥了, 他链接了两中模式, 如今让咱们一探究竟github
咱们将 redux 使用的流程分红 3 个模块redux
想要理解此中源码首先就须要理解不少 react hooks 的知识点 还有熟练使用 redux 的经验, 这里我就先简介一下设计模式
咱们要先理解一个设计模式 - 订阅发布模式 他位于文件: react-redux/src/utils/Subscription.js 具体的代码咱们会在后面细说api
关于 hooks 中 咱们须要了解到的知识点:缓存
具体的知识点还须要去官网了解: https://zh-hans.reactjs.org/d...babel
关于 store 的建立并发
store 使用的主要就是 redux 的 api, 无论 combineReducers
仍是 createStore
关于 redux 的 store 提供了如下 API:
export interface Store<S = any, A extends Action = AnyAction> { // dispatch 的动做 dispatch: Dispatch<A> // 返回应用程序的当前状态树。 getState(): S // 添加更改侦听器。每当分派动做时,都会调用它,而且状态树的某些部分可能已更改。而后,您能够调用`getState()`来读取回调中的当前状态树。 subscribe(listener: () => void): Unsubscribe // 替换 reducer replaceReducer(nextReducer: Reducer<S, A>): void }
通常来讲, 咱们会在项目入口处加上 Provider
, 如 index.js:
ReactDOM.render( <Provider store={store}> <App/> </Provider>, document.getElementById('root') );
Provider
接受一个 store
做为存储, 在 <App/>
中, 任意组件都能获取到 store
中的参数和方法
此外咱们还能 提供一个 context
给他, 可是通常不建议这样作, 若是不够熟悉, 会出现不少未知问题
能够查看文件: react-redux/src/components/Provider.js
//... // Provider 主体, 是一个组件, 一般在项目的入口使用 function Provider({ store, context, children }) { const contextValue = useMemo(() => { // 建立了一个订阅模式, 值为 store // 赋值 onStateChange 为 notifyNestedSubs, 做用 绑定了 store, 若是 store 值发生了变化 则执行 listener 里的所并回调 const subscription = new Subscription(store) subscription.onStateChange = subscription.notifyNestedSubs return { store, subscription, } }, [store]) // 用来获取store 的值 记录,做为对比 const previousState = useMemo(() => store.getState(), [store]) // useIsomorphicLayoutEffect 等于 useLayoutEffect useIsomorphicLayoutEffect(() => { const { subscription } = contextValue // 在 provider 里面 对于 store 添加 onStateChange 回调, 至关于 subscribe 包裹了一层函数, 这一层的做用后面会体如今 connect 中 // 除了添加回调 还初始化了 listeners subscribe 事件的机制 subscription.trySubscribe() if (previousState !== store.getState()) { // 当知青储存的值和当前值不一致时 触发 listeners 里的回调 subscription.notifyNestedSubs() } return () => { // 解除事件的监听 subscription.tryUnsubscribe() subscription.onStateChange = null } }, [contextValue, previousState]) // context, 若是外部提供了 则使用外部的 const Context = context || ReactReduxContext // 就是 context 的 provider return <Context.Provider value={contextValue}>{children}</Context.Provider> } // ...
到这里咱们就碰到了 Subscription
了, 如今须要知道的两点:
subscription.addNestedSub(listener)
函数, 添加监听事件subscription.notifyNestedSubs()
, 触发以前全部的监听事件subscription.trySubscribe()
属于第一点中函数的子函数, 效果出来不能添加回调之外,相似subscription.tryUnsubscribe()
, 与第三点相反, 解除监听如今, 咱们罗列下 Provider 作了什么事情:
真正的重头戏来了, 将 redux 的 store 与任意的组件链接
在这里咱们首先须要知道的是 connect
, 经过他是怎么使用的, 倒推回去看源码会更有帮助 他的定义:
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)
能够看到connect
可接受 4 个参数
mapStateToProps?: (state, ownProps?) => Object
他是一个函数, 接受 state 和 ownProps 两个参数, 返回一个对象, 若是 mapStateToProps 传递的是一个函数, 那么 store 更新的时候,包装的组件也会订阅更新 若是传递 undefined 或者
null, 能够避免不须要的更新
关于 ownProps
的用法, ownProps 其实就是组件的 props
const mapStateToProps = (state, ownProps) => ({ todo: state.todos[ownProps.id], })
mapDispatchToProps?: Object | (dispatch, ownProps?) => Object
第二个参数, 能够是函数, 能够是对象, 也能够是空值 若是是函数, 则能够收取到两个参数, dispatch
和 ownProps
一般咱们是这样作的:
const mapDispatchToProps = (dispatch) => { return { increment: () => dispatch({ type: 'INCREMENT' }), decrement: () => dispatch({ type: 'DECREMENT' }), } }
ownProps 的用法和 mapStateToProps 相同 当前参数若是是一个对象的时候,
须要控制里面的属性都是 action-creator
在源码中将会调用: bindActionCreators(mapDispatchToProps, dispatch)
来生成可用代码
官网中的简介: 点击查看
mergeProps?: (stateProps, dispatchProps, ownProps) => Object
这个参数的做用就是, 当前 connect 包装的组件, 对于他的 props 再次自定义 ,如不传递这个属性, 则代码中默认传递值为: { ...ownProps, ...stateProps, ...dispatchProps }
options?: Object
Object 中的内容:
{ context?: Object, pure?: boolean, areStatesEqual?: Function, areOwnPropsEqual?: Function, areStatePropsEqual?: Function, areMergedPropsEqual?: Function, forwardRef?: boolean, }
只有版本再 >=6.0 的时候才会有这个属性, 都是配置性的属性, 通常来讲默认值就能应付 99% 的状况了
更加具体的做用能够在此处点击查看: 点击查看
这是一个普通的用法:
connect(mapStateToProps, mapDispatchToProps)(App);
不难理解, connect 做为一个高阶函数, 返回的也是一个函数, 因此才会是这种用法
const connect = (mapStateToProps, mapDispatchToProps)=>{ return (Component) => { return <Conponent /> } }
具体应该就是这样, 如今带着咱们的理解和疑问再来进入 connect 源码
查看 connect 的入口文件 src/connect/connect
:
这个文件定义了一个 createConnect
函数, 这是用来生成 connect 的:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory, } = {}) { // 返回真正的 connect 函数 return function connect( mapStateToProps, mapDispatchToProps, mergeProps, { pure = true, areStatesEqual = strictEqual, areOwnPropsEqual = shallowEqual, areStatePropsEqual = shallowEqual, areMergedPropsEqual = shallowEqual, ...extraOptions } = {} ) { // 判断 mapStateToProps 是否符合已经定义的规则 // mapStateToPropsFactories 能够想象成你对 // mapStateToProps 作了一些判断, 只要有一个判断符合了 // 就能够成功返回值 // mapStateToPropsFactories 的规则会在 react-redux/src/connect/mapStateToProps.js 里讲解 // 默认的 defaultMapStateToPropsFactories 有两个规则 // 1. 若是是函数, 会使用 wrapMapToPropsFunc 包裹, 而且直接return结果 // 2. 若是没有传值, 则会使用 wrapMapToPropsConstant 包裹 const initMapStateToProps = match( mapStateToProps, mapStateToPropsFactories, 'mapStateToProps' ) // 同上 可是 他的默认规则是 defaultMapDispatchToPropsFactories // 在 react-redux/src/connect/mapDispatchToProps.js 此文件中 const initMapDispatchToProps = match( mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps' ) // 同上 const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps') // 包裹组件的高阶函数 connect(mapStateToProps, ...) return connectHOC(selectorFactory, { // 方便 error messages 打印 methodName: 'connect', // 用于从包装的组件的displayName计算Connect的displayName。 getDisplayName: (name) => `Connect(${name})`, // 若是mapStateToProps 为 falsy,则Connect组件不订阅存储状态更改 shouldHandleStateChanges: Boolean(mapStateToProps), // 传递给 selectorFactory 的参数 initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, // ...extraOptions, }) } }
defaultMapStateToPropsFactories
, defaultMapDispatchToPropsFactories
, defaultMergePropsFactories
, defaultSelectorFactory
咱们会放在下面研究, 如今先知道他是作什么的
一样的, 在这个文件 咱们能够看到 connect 的雏形了
真实的执行顺序: createConnect()
-> connect()
-> connectHOC()
= connectAdvanced()
-> wrapWithConnect(Component)
下一步就是 connectAdvanced()
中执行了什么:
这个咱们须要在 react-redux/src/components/connectAdvanced.js
这个文件中查看:
connectAdvanced
较为复杂, 咱们将它分段提取, 首先咱们来看他的传参
export default function connectAdvanced( // 这些是 connect 第一步中提供的参数 selectorFactory, // 默认为 defaultSelectorFactory // options object: { //用于从包装的组件的displayName计算此HOC的displayName的函数。 getDisplayName = (name) => `ConnectAdvanced(${name})`, // 在 error message 中显示 容易 debug methodName = 'connectAdvanced', // 没有太大做用, 后面可能会删除 renderCountProp = undefined, // 肯定这个 hoc 是否会监听 store 的改变 shouldHandleStateChanges = true, // 没有太大做用, 后面可能会删除 storeKey = 'store', // 没有太大做用, 后面可能会删除 withRef = false, // 是否使用了 forwardRef forwardRef = false, // 使用的 Context context = ReactReduxContext, //额外参数 ...connectOptions } = {} ) { // 省略了参数的校验 const Context = context return function wrapWithConnect(WrappedComponent) { // ... } }
这些参数都是能够在 createConnect
中找到, 能够看到 connectAdvanced
返回的 wrapWithConnect
, 就是咱们用来正真返回的, 用来包裹组件的函数
在 wrapWithConnect
也有一个主体函数 ConnectFunction
, 这里咱们先讲除此函数以外的做用
function wrapWithConnect(WrappedComponent) { // 省略校检 const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component' const displayName = getDisplayName(wrappedComponentName) // 上面两行都是获取组件名称 默认(Component) const selectorFactoryOptions = { ...connectOptions, getDisplayName, methodName, renderCountProp, shouldHandleStateChanges, storeKey, displayName, wrappedComponentName, WrappedComponent, } // 将 WrappedComponent 和 connectAdvanced中的参数集合在了一块儿 const {pure} = connectOptions // 第一步传递过来的参数 默认为 true // 建立子选择器的函数 声明 function createChildSelector(store) { return selectorFactory(store.dispatch, selectorFactoryOptions) } // 若是 pure 为 false, 则直接指向回调 而不是 useMemo const usePureOnlyMemo = pure ? useMemo : (callback) => callback() // 当前整个函数的主体部分 接受 props 返回 JSX 而且会用 Context 包裹 function ConnectFunction(props) { // 省略函数主体 } // 经过 pure 来肯定是否要加 memo const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction Connect.WrappedComponent = WrappedComponent Connect.displayName = displayName // forwardRef 省略 return hoistStatics(Connect, WrappedComponent) }
这里要说下 hoistStatics
, 他来自于 hoist-non-react-statics
这个库
简单的来讲能够当作 Object.assign
, 可是他是组件级别的
ConnectFunction
能够说是通过上一步的包装以后 真正在执行中的函数
function ConnectFunction(props) { const [ propsContext, reactReduxForwardedRef, wrapperProps, ] = useMemo(() => { // 区分传递给包装器组件的实际“数据”属性和控制行为所需的值(转发的引用,备用上下文实例)。 // 要维护wrapperProps对象引用,缓存此解构。 // 此处使用的是官方注释 const {reactReduxForwardedRef, ...wrapperProps} = props return [props.context, reactReduxForwardedRef, wrapperProps] }, [props]) const ContextToUse = useMemo(() => { // 用户能够选择传入自定义上下文实例来代替咱们的ReactReduxContext使用。 // 记住肯定应该使用哪一个上下文实例的检查。 // 此处使用的是官方注释 return propsContext && propsContext.Consumer && isContextConsumer(<propsContext.Consumer/>) ? propsContext : Context }, [propsContext, Context]) // useContext 不用多说 const contextValue = useContext(ContextToUse) // 到此处位置都是 context 的预备工做 // store 必须存在于 props 或 context // 咱们将首先检查它是否看起来像 Redux store。 // 这使咱们能够经过一个 “store” props,该 props 只是一个简单的值。 const didStoreComeFromProps = Boolean(props.store) && Boolean(props.store.getState) && Boolean(props.store.dispatch) // 确认 store 是否来自于本地 context const didStoreComeFromContext = Boolean(contextValue) && Boolean(contextValue.store) //省略报错判断 // 获取 store 赋值 const store = didStoreComeFromProps ? props.store : contextValue.store // 到这是 store 的判断 const childPropsSelector = useMemo(() => { // 子道具选择器须要store参考做为输入。每当store更改时,则从新建立此选择器。 return createChildSelector(store) }, [store]) const [subscription, notifyNestedSubs] = useMemo(() => { // 肯定这个 hoc 是否会监听 store 的改变 默认为 true if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY // [null, null] // new 一个新的 Subscription // 此订阅的来源应与存储来自何处相匹配:store vs context。 // 经过 props 链接到 store 的组件不该使用 context 订阅,反之亦然。 // 文件来源 react-redux/src/utils/Subscription.js const subscription = new Subscription( store, didStoreComeFromProps ? null : contextValue.subscription ) // `notifyNestedSubs`是重复的,以处理组件在通知循环中间被取消订阅的状况, // 此时`subscription`将为空。 若是修改Subscription的监听器逻辑, // 不在通知循环中间调用已取消订阅的监听器,就能够避免这种状况。 // 此处使用的是官方注释 const notifyNestedSubs = subscription.notifyNestedSubs.bind( subscription ) return [subscription, notifyNestedSubs] }, [store, didStoreComeFromProps, contextValue]) // 若是须要的话,肯定应该把什么{store,subscription}值放到嵌套的context中 // ,并将该值备忘,以免没必要要的上下文更新。 const overriddenContextValue = useMemo(() => { if (didStoreComeFromProps) { // 这个组件是直接从props订阅一个存储. // 咱们不但愿子孙从这个存储中读取--不管现有的上下文值是来自最近的链接祖先的什么, // 都会传下来。 return contextValue } // 不然,把这个组件的订阅实例放到上下文中,这样链接的子孙就不会更新,直到这个组件完成以后。 return { ...contextValue, subscription, } }, [didStoreComeFromProps, contextValue, subscription]) // 每当 Redux store 更新致使计算出的子组件 props 发生变化时,咱们须要强制这个包装组件从新渲染(或者咱们在mapState中发现了一个错误)。 const [ [previousStateUpdateResult], forceComponentUpdateDispatch, ] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates) // 抛出任何 mapState/mapDispatch 错误。 if (previousStateUpdateResult && previousStateUpdateResult.error) { throw previousStateUpdateResult.error } // 设置 ref,以协调订阅效果和渲染逻辑之间的数值。 // 参考 经过 ref 能够获取,存储值 const lastChildProps = useRef() const lastWrapperProps = useRef(wrapperProps) const childPropsFromStoreUpdate = useRef() const renderIsScheduled = useRef(false) const actualChildProps = usePureOnlyMemo(() => { // 这里的逻辑很复杂: // 这个渲染多是由 Redux store 更新所触发,产生了新的子 props。 // 不过,在那以后,咱们可能会获得新的包装 props。 // 若是咱们有新的子 props ,和相同的包装 props , 咱们知道咱们应该按原样使用新的子 props . // 可是,若是咱们有新的包装props,这些可能会改变子 props ,因此咱们必须从新计算这些. // 因此,只有当包装 props 和上次同样时,咱们才会使用 store 更新的子 props。 if ( childPropsFromStoreUpdate.current && wrapperProps === lastWrapperProps.current ) { return childPropsFromStoreUpdate.current } // 这极可能会致使在并发模式下发生坏事(TM)。 // 请注意,咱们之因此这样作是由于在由存储更新引发的渲染中, // 咱们须要最新的存储状态来肯定子 props 应该是什么。 return childPropsSelector(store.getState(), wrapperProps) }, [store, previousStateUpdateResult, wrapperProps]) // 咱们须要在每次从新渲染时同步执行。 // 然而,React会对SSR中的useLayoutEffect发出警告, 避免警告 // 至关于在 useLayoutEffect 中执行, 包装了一下 // 第一个参数是待执行函数, 第二个是函数参数, 第三个依赖 useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [ lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, actualChildProps, childPropsFromStoreUpdate, notifyNestedSubs, ]) // 咱们的从新订阅逻辑只有在 store或者订阅设置发生变化时才会运行。 useIsomorphicLayoutEffectWithArgs( subscribeUpdates, [ shouldHandleStateChanges, store, subscription, childPropsSelector, lastWrapperProps, lastChildProps, renderIsScheduled, childPropsFromStoreUpdate, notifyNestedSubs, forceComponentUpdateDispatch, ], [store, subscription, childPropsSelector] ) // 如今全部这些都完成了,咱们终于能够尝试实际渲染子组件了。 // 咱们将渲染后的子组件的元素进行记忆,做为一种优化。 const renderedWrappedComponent = useMemo( () => ( <WrappedComponent {...actualChildProps} ref={reactReduxForwardedRef} /> ), [reactReduxForwardedRef, WrappedComponent, actualChildProps] ) // 若是React看到了与上次彻底相同的元素引用,它就会退出从新渲染该子元素,就像在React.memo()中被包裹或从shouldComponentUpdate中返回false同样。 const renderedChild = useMemo(() => { // 肯定这个 hoc 是否会监听 store 的改变, 默认是 true if (shouldHandleStateChanges) { // 若是这个组件订阅了存储更新,咱们须要将它本身的订阅实例传递给咱们的子孙。 // 这意味着渲染相同的Context实例,并将不一样的值放入context中。 return ( <ContextToUse.Provider value={overriddenContextValue}> {renderedWrappedComponent} </ContextToUse.Provider> ) } return renderedWrappedComponent }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]) return renderedChild }
这一部分即是 connect 的核心代码
再肢解一下上面的代码可分为一下几个步骤:
肯定 Context -> 肯定 store 来源 -> 将一个订阅,发布合并到 contextValue 中 -> 组件更新后, 检查 store 值是否变化 -> 返回包装组件
再解释这部分代码中的引用的部分函数: captureWrapperProps
, subscribeUpdates
代码是在这里:
useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [ lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, actualChildProps, childPropsFromStoreUpdate, notifyNestedSubs, ])
转换一下:
useLayoutEffect(() => { captureWrapperProps( lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, actualChildProps, childPropsFromStoreUpdate, notifyNestedSubs ) }) function captureWrapperProps( lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, actualChildProps, childPropsFromStoreUpdate, notifyNestedSubs ) { lastWrapperProps.current = wrapperProps lastChildProps.current = actualChildProps renderIsScheduled.current = false // 若是渲染是来自store的更新,则清除该引用 而且触发订阅 if (childPropsFromStoreUpdate.current) { childPropsFromStoreUpdate.current = null notifyNestedSubs() } }
源码:
useIsomorphicLayoutEffectWithArgs( subscribeUpdates, [ shouldHandleStateChanges, store, subscription, childPropsSelector, lastWrapperProps, lastChildProps, renderIsScheduled, childPropsFromStoreUpdate, notifyNestedSubs, forceComponentUpdateDispatch, ], [store, subscription, childPropsSelector] )
一样地通过转换:
useLayoutEffect(() => { subscribeUpdates( shouldHandleStateChanges, store, subscription, childPropsSelector, lastWrapperProps, lastChildProps, renderIsScheduled, childPropsFromStoreUpdate, notifyNestedSubs, forceComponentUpdateDispatch, ) }, [store, subscription, childPropsSelector])
咱们再看 subscribeUpdates
作了什么, 这里就比较复杂了:
function subscribeUpdates( shouldHandleStateChanges, store, subscription, childPropsSelector, lastWrapperProps, lastChildProps, renderIsScheduled, childPropsFromStoreUpdate, notifyNestedSubs, forceComponentUpdateDispatch ) { // 若是不想从 store 中更新, 则直接返回 if (!shouldHandleStateChanges) return let didUnsubscribe = false let lastThrownError = null // 每次 store 的订阅更新传播到这个组件时,咱们都会运行这个回调。 const checkForUpdates = () => { if (didUnsubscribe) { // Redux不能保证取消订阅会在下一次发送以前发生。 return } const latestStoreState = store.getState() let newChildProps, error try { // 用最新的store状态和包装运行选择器 newChildProps = childPropsSelector( latestStoreState, lastWrapperProps.current ) } catch (e) { error = e lastThrownError = e } if (!error) { lastThrownError = null } // 若是没变化就不作什么 if (newChildProps === lastChildProps.current) { if (!renderIsScheduled.current) { notifyNestedSubs() } } else { // 保存对新的子props的引用。 lastChildProps.current = newChildProps childPropsFromStoreUpdate.current = newChildProps renderIsScheduled.current = true // If the child props _did_ change (or we caught an error), this wrapper component needs to re-render // 若是 子 props 确实发生了变化, 那么 wrapperComponent 须要重渲染 forceComponentUpdateDispatch({ type: 'STORE_UPDATED', payload: { error, }, }) } } subscription.onStateChange = checkForUpdates subscription.trySubscribe() // 执行 checkForUpdates() // 在第一次渲染后从store拉出数据,以防store在咱们开始后发生变化。 const unsubscribeWrapper = () => { didUnsubscribe = true subscription.tryUnsubscribe() subscription.onStateChange = null // 若是出错, 可是到此声明周期还没解决, 就触发报错 if (lastThrownError) { throw lastThrownError } } return unsubscribeWrapper }
从这几行能够看出来, store 或者 props 的变化都会致使此包装组件的再渲染, 选渲染中又加上了判断, 能够控制子组件是否真的可以渲染
这函数是获取 store 的, 以前使用的地方
// childPropsSelector使用 1 newChildProps = childPropsSelector( latestStoreState, lastWrapperProps.current ) // childPropsSelector使用 2 childPropsSelector(store.getState(), wrapperProps) const childPropsSelector = useMemo(() => { return createChildSelector(store) }, [store]) function createChildSelector(store) { return selectorFactory(store.dispatch, selectorFactoryOptions) }
在默认状况下 selectorFactory = defaultSelectorFactory
源文件: react-redux/src/connect/selectorFactory.js
defaultSelectorFactory
别名: finalPropsSelectorFactory
// 若是pure为true,则selectorFactory返回的选择器将记住其结果, // 若是未更改结果,则connectAdvanced的shouldComponentUpdate能够返回false。 // 若是为false,则选择器将始终返回新对象,而shouldComponentUpdate将始终返回true。 // 默认的选择器工厂 export default function finalPropsSelectorFactory( dispatch, { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options } ) { // initMapStateToProps 可在 connect 中查看 就是经过 match 获取的结果 const mapStateToProps = initMapStateToProps(dispatch, options) const mapDispatchToProps = initMapDispatchToProps(dispatch, options) const mergeProps = initMergeProps(dispatch, options) // 忽略验证 const selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory return selectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options ) }
这里再次进行到下一流程 initMapStateToProps
, initMapDispatchToProps
, initMergeProps
:
这三个变量的来源是在这里:
const initMapStateToProps = match( mapStateToProps, mapStateToPropsFactories, 'mapStateToProps' ) const initMapDispatchToProps = match( mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps' ) const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
而这, 咱们又接触到了新的几个参数 match
, mapStateToPropsFactories
,mapDispatchToPropsFactories
,mergePropsFactories
:
先说说 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 }.` ) } }
这个的做用,咱们以前也略微讲过, 就是经过执行 factories 中的函数, 若是有返回值则返回对应的值
而这里咱们也须要说下这几个 Factories
: defaultMapStateToPropsFactories
, defaultMapDispatchToPropsFactories
, defaultMergePropsFactories
export function whenMapStateToPropsIsFunction(mapStateToProps) { return typeof mapStateToProps === 'function' ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps') : undefined } export function whenMapStateToPropsIsMissing(mapStateToProps) { return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined } export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]
通过以前咱们的 connect
用法的介绍:
mapStateToProps?: (state, ownProps?) => Object
若是 mapStateToProps
传的是一个函数, 则用 wrapMapToPropsFunc
包裹, 否则就包裹一个空函数
咱们再来看下 wrapMapToPropsFunc
:
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) } 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) } // 注释验证 return props } return proxy } } // dependsOnOwnProps默认为 true // 判断 mapToProps 的dependsOnOwnProps属性是否为空, // 若是不为空则, 则返回 Boolean(dependsOnOwnProps), 若是为空, 则比较后返回 布尔值 function getDependsOnOwnProps(mapToProps) { return mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined ? Boolean(mapToProps.dependsOnOwnProps) : mapToProps.length !== 1 }
经历过 match 的遍历, 返回的就是 initProxySelector
, 这个地方设计得很巧妙initProxySelector
的时候, 传入值: dispatch, options
, 这里 options 能够暂时忽略 , 这里是有mapToProps的入参
他的返回结果也是一个函数, 即 proxy 函数
执行的是 proxy.mapToProps(stateOrDispatch, ownProps)
即 detectFactoryAndVerify
覆盖原 mapToProps
: proxy.mapToProps = mapToProps
这里覆盖的就是咱们传入的 mapStateToProps
函数 / 或者 undefined, proxy.dependsOnOwnProps
正常状况下都是返回 true
这时候 再次执行 proxy
: let props = proxy(stateOrDispatch, ownProps)
转换一下: mapToProps(stateOrDispatch, ownProps)
, 这里的 mapToProps
是咱们传入的,
以后继续往下走:
if (typeof props === 'function') { proxy.mapToProps = props proxy.dependsOnOwnProps = getDependsOnOwnProps(props) props = proxy(stateOrDispatch, ownProps) }
这里是对于返回结果又作了一层判断, 若是返回的是一个函数, 将会覆盖
由于这个判断, 因此咱们能够这样传 mapStateToProps
:
const mapStateToProps = (_state) => { return (state) => ({ value: state.value }) }
因此说查看源码, 是能够发现一些新的用法的, 虽然这样的写法不是很提倡, 暂时没什么做用, 但为后面他的拓展性作了很充足的准备
这个参数是和 defaultMapStateToPropsFactories
相似了
并且由于 mapDispatchToProps
能够传入 Object
, 在 match
上又会多一层判断
这里涉及到的代码和 defaultMapStateToPropsFactories
90% 都是相似了, 因此跳过
同上, 基本相似, 也再也不赘述
到这里继续讲 selectorFactory
, 通常来讲 selectorFactory
运行的都是此函数 pureFinalPropsSelectorFactory
,
代码一样是在 react-redux/src/connect/selectorFactory.js
此文件夹下的
入参:
mapStateToProps, mapDispatchToProps, mergeProps, dispatch, { areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
在此函数体内, 他又定义了几个函数, 最关键是这个:
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) { // 表示是否已经运行过一次, 若是没有,则使用 handleFirstCall 初始化 // 不然使用 handleSubsequentCalls return hasRunAtLeastOnce ? handleSubsequentCalls(nextState, nextOwnProps) : handleFirstCall(nextState, nextOwnProps) }
这里的比较方法areOwnPropsEqual
,areStatesEqual
默认都是 shallowEqual
(来自文件: react-redux/src/utils/shallowEqual.js), 算是一个浅层比较
经过比较 新旧 props 和 state, 若是发生变化, 则进行对应的更新最终返回合并后的值
咱们传递的 mapStateToProps
->
通过 match 函数
-> match
函数中的 wrapMapToPropsFunc
->
如今执行的是 initProxySelector
->
别名 initMapStateToProps
->
经过执行他 得到结果 const mapStateToProps = initMapStateToProps(dispatch, options)
->
经过 finalPropsSelectorFactory
的包装 ->
别名 selectorFactory
->
在函数中杯执行 selectorFactory(store.dispatch, selectorFactoryOptions)
->
返回的值, 做为 childPropsSelector
的值 ->
在新旧 props 比较时(subscribeUpdates中)使对此这个值
本文较为简单的解析了一下 react-redux
这个经常使用库, 总结了几种代码运行流程
createConnect()
-> connect()
-> connectHOC()
= connectAdvanced()
-> wrapWithConnect(Component)
mapStateToProps
-> match 函数
-> match
函数中的 wrapMapToPropsFunc
-> initProxySelector
-> initMapStateToProps
-> const mapStateToProps = initMapStateToProps(dispatch, options)
-> finalPropsSelectorFactory
的包装 -> selectorFactory
-> selectorFactory(store.dispatch, selectorFactoryOptions)
-> childPropsSelector
的值 -> 本文记录:
https://github.com/Grewer/rea...
参考文档:
https://react-redux.js.org/in...
https://github.com/reduxjs/re...