• 阅读react-redux源码 - 零
  • 阅读react-redux源码 - 一
  • 阅读react-redux源码(二) - createConnect、match函数的实现
  • 阅读react-redux源码(三) - mapStateToPropsFactories、mapDispatchToPropsFactories和mergePropsFactories
  • 阅读react-redux源码(四) - connectAdvanced、wrapWithConnect、ConnectFunction和checkForUpdates

终于到了最核心的connectAdvanced.js文件,在这里做的最主要的事情就是响应Provider提供的store的改变。除了响应store的变化,还做了很多事情,分别是:ref的处理,是否是pure模式,还有对于store改变的事件的转发。

首先回顾下connectAdvanced函数在connect.js中是如何被使用的。

return connectHOC(selectorFactory, {// used in error messagesmethodName: '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 changesshouldHandleStateChanges: Boolean(mapStateToProps),// passed through to selectorFactoryinitMapStateToProps,initMapDispatchToProps,initMergeProps,pure,areStatesEqual,areOwnPropsEqual,areStatePropsEqual,areMergedPropsEqual,// any extra options args can override defaults of connect or connectAdvanced...extraOptions
})

connect函数调用后返回connectHOC调用后的返回值

而connect函数是这样用的:

connect(mapStateToProps, mapDispatchToProps)(wrappedComponent)

connect的返回值就是connectHOC的返回值,所以返回值一定也是一个方法。connectHOCReturnValue(wrappedComponent)

connectAdvanced

继续来看connectAdvanced.js文件中connectAdvanced方法的实现。因为整个方法的实现异常复杂,所以我决定分开叙述,以功能点的方式进行,首先最重要的当然是如何响应store中的变化

function connectAdvanced (selectorFactory,{// the func used to compute this HOC's displayName from the wrapped component's displayName.// probably overridden by wrapper functions such as connect()getDisplayName = name => `ConnectAdvanced(${name})`,// shown in error messages// probably overridden by wrapper functions such as connect()methodName = 'connectAdvanced',// REMOVED: if defined, the name of the property passed to the wrapped element indicating the number of// calls to render. useful for watching in react devtools for unnecessary re-renders.renderCountProp = undefined,// determines whether this HOC subscribes to store changesshouldHandleStateChanges = true,// REMOVED: the key of props/context to get the storestoreKey = 'store',// REMOVED: expose the wrapped component via refswithRef = false,// use React's forwardRef to expose a ref of the wrapped componentforwardRef = false,// the context consumer to usecontext = ReactReduxContext,// additional options are passed through to the selectorFactory...connectOptions} = {}
) {...return function wrapWithConnect(WrappedComponent) {...}
}

wrapWithConnect函数就是connectAdvanced函数的返回值,也就是 connect(mapStateToProps, mapDispatchToProps)的返回值,入参WrappedComponent就是我们的业务组件(需要连接到store的组件)。

wrapWithConnect

return function wrapWithConnect(WrappedComponent) {...const selectorFactoryOptions = {...connectOptions,getDisplayName,methodName,renderCountProp,shouldHandleStateChanges,storeKey,displayName,wrappedComponentName,WrappedComponent}...function createChildSelector(store) {return selectorFactory(store.dispatch, selectorFactoryOptions)}...function ConnectFunction(props) {...}const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction...return hoistStatics(Connect, WrappedComponent)
}

函数wrapWithConnect的入参是我们的业务组件,返回值是一个内部组件 Connect,return hoistStatics(Connect, WrappedComponent)hoistStatics函数主要作用是复制函数的静态属性,本例中是将WrappedComponent的静态属性复制到Connect组件上(Connect就是 ConnectFunction)。

核心中的核心 ConnectFunction 函数

通过其大写的首字母就知道是一个React组件。这个组件就是真正被导出的组件。看看是怎么实现的,如何连接到store的。

function ConnectFunction(props) {...const contextValue = useContext(ContextToUse)...const store = didStoreComeFromProps ? props.store : contextValue.store...const childPropsSelector = useMemo(() => {// The child props selector needs the store reference as an input.// Re-create this selector whenever the store changes.return createChildSelector(store)}, [store])...const [subscription, notifyNestedSubs] = useMemo(() => {if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY// This Subscription's source should match where store came from: props vs. context. A component// connected to the store via props shouldn't use subscription from context, or vice versa.const subscription = new Subscription(store,didStoreComeFromProps ? null : contextValue.subscription)// `notifyNestedSubs` is duplicated to handle the case where the component is unmounted in// the middle of the notification loop, where `subscription` will then be null. This can// probably be avoided if Subscription's listeners logic is changed to not call listeners// that have been unsubscribed in the  middle of the notification loop.const notifyNestedSubs = subscription.notifyNestedSubs.bind(subscription)return [subscription, notifyNestedSubs]}, [store, didStoreComeFromProps, contextValue])...const [[previousStateUpdateResult],forceComponentUpdateDispatch] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)if (previousStateUpdateResult && previousStateUpdateResult.error) {throw previousStateUpdateResult.error}// Set up refs to coordinate values between the subscription effect and the render logicconst lastChildProps = useRef()const lastWrapperProps = useRef(wrapperProps)const childPropsFromStoreUpdate = useRef()const renderIsScheduled = useRef(false)const actualChildProps = usePureOnlyMemo(() => {// Tricky logic here:// - This render may have been triggered by a Redux store update that produced new child props// - However, we may have gotten new wrapper props after that// If we have new child props, and the same wrapper props, we know we should use the new child props as-is.// But, if we have new wrapper props, those might change the child props, so we have to recalculate things.// So, we'll use the child props from store update only if the wrapper props are the same as last time.if (childPropsFromStoreUpdate.current &&wrapperProps === lastWrapperProps.current) {return childPropsFromStoreUpdate.current}// TODO We're reading the store directly in render() here. Bad idea?// This will likely cause Bad Things (TM) to happen in Concurrent Mode.// Note that we do this because on renders _not_ caused by store updates, we need the latest store state// to determine what the child props should be.return childPropsSelector(store.getState(), wrapperProps)}, [store, previousStateUpdateResult, wrapperProps])// We need this to execute synchronously every time we re-render. However, React warns// about useLayoutEffect in SSR, so we try to detect environment and fall back to// just useEffect instead to avoid the warning, since neither will run anyway.useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [lastWrapperProps,lastChildProps,renderIsScheduled,wrapperProps,actualChildProps,childPropsFromStoreUpdate,notifyNestedSubs])// Our re-subscribe logic only runs when the store/subscription setup changesuseIsomorphicLayoutEffectWithArgs(subscribeUpdates,[shouldHandleStateChanges,store,subscription,childPropsSelector,lastWrapperProps,lastChildProps,renderIsScheduled,childPropsFromStoreUpdate,notifyNestedSubs,forceComponentUpdateDispatch],[store, subscription, childPropsSelector])// Now that all that's done, we can finally try to actually render the child component.// We memoize the elements for the rendered child component as an optimization.const renderedWrappedComponent = useMemo(() => <WrappedComponent {...actualChildProps} ref={forwardedRef} />,[forwardedRef, WrappedComponent, actualChildProps])// If React sees the exact same element reference as last time, it bails out of re-rendering// that child, same as if it was wrapped in React.memo() or returned false from shouldComponentUpdate.const renderedChild = useMemo(() => {if (shouldHandleStateChanges) {// If this component is subscribed to store updates, we need to pass its own// subscription instance down to our descendants. That means rendering the same// Context instance, and putting a different value into the context.return (<ContextToUse.Provider value={overriddenContextValue}>{renderedWrappedComponent}</ContextToUse.Provider>)}return renderedWrappedComponent}, [ContextToUse, renderedWrappedComponent, overriddenContextValue])return renderedChild
}

首先从contextValue中取出store。然后拿到childPropsSelector(用于计算正真需要注入业务组件的完整props),该函数通过store.getState()wrapperProps来计算出新的actualChildProps用于渲染子组件。

再往下看得到了[subscription, notifyNestedSubs]这两个名字应该很熟悉了在阅读react-redux源码 - 一中有详细的介绍。这里的subscripion也是一个事件对象,而notifyNestedSubs可以通知subscription的所有监听者,事件发生,执行回调。

const subscription = new Subscription(store,didStoreComeFromProps ? null : contextValue.subscription
)

这里的subscription监听了contextValue.subscription的变化,或者store的变化(这里后面详细介绍)。

const [[previousStateUpdateResult],forceComponentUpdateDispatch
] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)

这个是引起组件 ConnectFunction更新的关键,只有调用forceComponentUpdateDispatch函数,组件ConnectFunction才会更新。

storeStateUpdatesReducer

function storeStateUpdatesReducer(state, action) {const [, updateCount] = statereturn [action.payload, updateCount]
}

storeStateUpdatesReducer直接返回一个数组,第一项就是dispatch的action的payload,也就是说 forceComponentUpdateDispatch 入参的payload属性是什么那么previousStateUpdateResult值就是什么。

如果previousStateUpdateResult.error有值表示发生错误,直接抛出去,不要继续了。

if (previousStateUpdateResult && previousStateUpdateResult.error) {throw previousStateUpdateResult.error
}

下面定义了几个ref:

const lastChildProps = useRef()
const lastWrapperProps = useRef(wrapperProps)
const childPropsFromStoreUpdate = useRef()
const renderIsScheduled = useRef(false)

lastChildProps:最后的childProps(传递给WrappedComponent的props,这里记录的是上一次的props)

lastWrapperProps:父元素传递进来的props

childPropsFromStoreUpdate:store更新计算出来的childProps

renderIsScheduled:是否在render中

下面开始计算真实的childProps也就是actualChildProps值:

const actualChildProps = usePureOnlyMemo(() => {// Tricky logic here:// - This render may have been triggered by a Redux store update that produced new child props// - However, we may have gotten new wrapper props after that// If we have new child props, and the same wrapper props, we know we should use the new child props as-is.// But, if we have new wrapper props, those might change the child props, so we have to recalculate things.// So, we'll use the child props from store update only if the wrapper props are the same as last time.if (childPropsFromStoreUpdate.current &&wrapperProps === lastWrapperProps.current) {return childPropsFromStoreUpdate.current}// TODO We're reading the store directly in render() here. Bad idea?// This will likely cause Bad Things (TM) to happen in Concurrent Mode.// Note that we do this because on renders _not_ caused by store updates, we need the latest store state// to determine what the child props should be.return childPropsSelector(store.getState(), wrapperProps)
}, [store, previousStateUpdateResult, wrapperProps])

首先查看是否是store变动引起的更新,如果是,还需要查来自父元素的props是否没有更新过,如果是,则直接返回store更新计算出来的childProps,否则弃用store更新计算出来的childProps(childPropsFromStoreUpdate.current)重新通过childPropsSelector(store.getState(), wrapperProps)计算childProps。

 useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [lastWrapperProps,lastChildProps,renderIsScheduled,wrapperProps,actualChildProps,childPropsFromStoreUpdate,notifyNestedSubs])

这个其实就是在缓存数据,为了缓存本轮更新的值,在下一次更新的时候可以拿到现在的值。

captureWrapperProps

function captureWrapperProps(lastWrapperProps,lastChildProps,renderIsScheduled,wrapperProps,actualChildProps,childPropsFromStoreUpdate,notifyNestedSubs
) {// We want to capture the wrapper props and child props we used for later comparisonslastWrapperProps.current = wrapperPropslastChildProps.current = actualChildPropsrenderIsScheduled.current = false// If the render was from a store update, clear out that reference and cascade the subscriber updateif (childPropsFromStoreUpdate.current) {childPropsFromStoreUpdate.current = nullnotifyNestedSubs()}
}

这里做了两件事情,一个是缓存了wrapperProps值,actualChildProps值和将是否在渲染重置为false。第二个是检查是否是store变更引起的更新,如果是则通知subscription的订阅者需要拉取最新的state。

在往下就实现了订阅更新了,关联起上面的subscription实例和上面提到的更新组件的唯一方法 forceComponentUpdateDispatch

function subscribeUpdates(shouldHandleStateChanges,store,subscription,childPropsSelector,lastWrapperProps,lastChildProps,renderIsScheduled,childPropsFromStoreUpdate,notifyNestedSubs,forceComponentUpdateDispatch
) {// If we're not subscribed to the store, nothing to do hereif (!shouldHandleStateChanges) return// Capture values for checking if and when this component unmountslet didUnsubscribe = falselet lastThrownError = null// We'll run this callback every time a store subscription update propagates to this componentconst checkForUpdates = () => {if (didUnsubscribe) {// Don't run stale listeners.// Redux doesn't guarantee unsubscriptions happen until next dispatch.return}const latestStoreState = store.getState()let newChildProps, errortry {// Actually run the selector with the most recent store state and wrapper props// to determine what the child props should benewChildProps = childPropsSelector(latestStoreState,lastWrapperProps.current)} catch (e) {error = elastThrownError = e}if (!error) {lastThrownError = null}// If the child props haven't changed, nothing to do here - cascade the subscription updateif (newChildProps === lastChildProps.current) {if (!renderIsScheduled.current) {notifyNestedSubs()}} else {// Save references to the new child props.  Note that we track the "child props from store update"// as a ref instead of a useState/useReducer because we need a way to determine if that value has// been processed.  If this went into useState/useReducer, we couldn't clear out the value without// forcing another re-render, which we don't want.lastChildProps.current = newChildPropschildPropsFromStoreUpdate.current = newChildPropsrenderIsScheduled.current = true// If the child props _did_ change (or we caught an error), this wrapper component needs to re-renderforceComponentUpdateDispatch({type: 'STORE_UPDATED',payload: {error}})}}// Actually subscribe to the nearest connected ancestor (or store)subscription.onStateChange = checkForUpdatessubscription.trySubscribe()// Pull data from the store after first render in case the store has// changed since we began.checkForUpdates()const unsubscribeWrapper = () => {didUnsubscribe = truesubscription.tryUnsubscribe()subscription.onStateChange = nullif (lastThrownError) {// It's possible that we caught an error due to a bad mapState function, but the// parent re-rendered without this component and we're about to unmount.// This shouldn't happen as long as we do top-down subscriptions correctly, but// if we ever do those wrong, this throw will surface the error in our tests.// In that case, throw the error from here so it doesn't get lost.throw lastThrownError}}return unsubscribeWrapper
}

这个函数里主要是将 subscription 的change关联到forceComponentUpdateDispatch上,实现方式如下:

subscription.onStateChange = checkForUpdates
subscription.trySubscribe()checkForUpdates()

checkForUpdates

首先根据最新的state和最新的wrapperProps来计算出newChildProps,如果计算出来的childProps和上一次的childProps一样,那么当前组件不必更新。但是当组件不需要更新的时候则需要单独通知subscription的监听者,state是有更新的,因为每个组件监听的state是不一样的,虽然当前组件没有更新,但是别的组件会获取到新的state用以更新。

如果不一样则需要更新:

lastChildProps.current = newChildProps
childPropsFromStoreUpdate.current = newChildProps
renderIsScheduled.current = trueforceComponentUpdateDispatch({type: 'STORE_UPDATED',payload: {error}
})

需要更新当前组件需要调用方法forceComponentUpdateDispatch,并且设置缓存上lastChildProps.current = newChildPropschildPropsFromStoreUpdate.current = newChildProps,其中childPropsFromStoreUpdate.current会在 forceComponentUpdateDispatch一起的下一次更新时候通知captureWrapperProps函数需要notifyNestedSubs,通知subscription对象有state更新。

到这里说完了store更新的流程:store更新会触发subscription的onStateChange也就是上面说的checkForUpdates方法,该方法会检查当前组件订阅的state和wrapperProps生成的childProps是否改变了,如果改变需要通知更新当前组件,如果没有改变需要通知自己的订阅者有新的state产生。

#wrapperProps更新引起的组件更新

const actualChildProps = usePureOnlyMemo(() => {if (childPropsFromStoreUpdate.current &&wrapperProps === lastWrapperProps.current) {return childPropsFromStoreUpdate.current}return childPropsSelector(store.getState(), wrapperProps)
}, [store, previousStateUpdateResult, wrapperProps])

父组件传入的props更新的话会重新计算actualChildProps,然后传给组件WrappedComponent。

单独由父组件传入的props更新导致的组件更新,childPropsFromStoreUpdate.current 值一定为假所以执行的是 childPropsSelector(store.getState(), wrapperProps)计算新的 actualChildProps

当然wrapperProps的更新还会执行函数captureWrapperProps来捕获新的值,用于下次对比。

renderIsScheduled

这个标识符表示的是否在更新中,我们知道react的update是“异步”的,所以当连续多次dispatch的时候:

newChildProps === lastChildProps.current这个等式可能会多次成立(dispatch(1); dispatch(1);)。如果没有 !renderIsScheduled.current控制会导致 notifyNestedSubs()多次执行,这是不必要的,因为后面的:

function captureWrapperProps() {...if (childPropsFromStoreUpdate.current) {childPropsFromStoreUpdate.current = nullnotifyNestedSubs()}...}

更新结束后会一次性通知有state改变。

继续往下看:

function ConnectFunction(props) {...const renderedWrappedComponent = useMemo(() => <WrappedComponent {...actualChildProps} ref={forwardedRef} />,[forwardedRef, WrappedComponent, actualChildProps])const renderedChild = useMemo(() => {if (shouldHandleStateChanges) {// If this component is subscribed to store updates, we need to pass its own// subscription instance down to our descendants. That means rendering the same// Context instance, and putting a different value into the context.return (<ContextToUse.Provider value={overriddenContextValue}>{renderedWrappedComponent}</ContextToUse.Provider>)}return renderedWrappedComponent}, [ContextToUse, renderedWrappedComponent, overriddenContextValue])return renderedChild
}

ConnectFunction组件最后返回了组件renderedChild,而该组件则是renderedWrappedComponent是一个被缓存的组件:<WrappedComponent {...actualChildProps} ref={forwardedRef} />可以看出来actualChildProps这个计算出来的总属性被注入给了我们的业务组件。

以上就是从订阅store更新以更新组件自身的角度看了函数connectAdvanced,接下来将从生下的角度查看该函数做的事情。

  • 阅读react-redux源码 - 零
  • 阅读react-redux源码 - 一
  • 阅读react-redux源码(二) - createConnect、match函数的实现
  • 阅读react-redux源码(三) - mapStateToPropsFactories、mapDispatchToPropsFactories和mergePropsFactories
  • 阅读react-redux源码(四) - connectAdvanced、wrapWithConnect、ConnectFunction和checkForUpdates

阅读react-redux源码(四) - connectAdvanced、wrapWithConnect、ConnectFunction和checkForUpdates相关推荐

  1. 阅读react-redux源码(五) - connectAdvanced中store改变的事件转发、ref的处理和pure模式的处理

    阅读react-redux源码 - 零 阅读react-redux源码 - 一 阅读react-redux源码(二) - createConnect.match函数的实现 阅读react-redux源 ...

  2. 逐行阅读redux源码(二)combineReducers

    前情提要 逐行阅读redux源码(一)createStore 认识reducers 在我们开始学习源码之前,我们不妨先来看看何谓reducers: 如图所见,我们可以明白, reducer 是用来对初 ...

  3. [react] 你阅读了几遍React的源码?都有哪些收获?你是怎么阅读的?

    [react] 你阅读了几遍React的源码?都有哪些收获?你是怎么阅读的? 0遍 +1 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易, 但坚持一定很酷.欢迎大家一起讨论 主目录 与歌 ...

  4. Redux 源码解读 —— 从源码开始学 Redux

    已经快一年没有碰过 React 全家桶了,最近换了个项目组要用到 React 技术栈,所以最近又复习了一下:捡起旧知识的同时又有了一些新的收获,在这里作文以记之. 在阅读文章之前,最好已经知道如何使用 ...

  5. 学习 redux 源码整体架构,深入理解 redux 及其中间件原理

    如果觉得内容不错,可以设为星标置顶我的公众号 1. 前言 你好,我是若川.这是学习源码整体架构系列第八篇.整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是 ...

  6. 中秋福利 | 漂亮的React后台源码真情大放送

    中秋快乐 每逢佳节倍思亲,一年一度的中秋,你和谁一起度过?如果你和小编一样,漂泊在外,别忘记给远在家乡的父母打个电话,祝福他们中秋快乐,告诉他们自己还好,勿让他们挂念.在此小编,祝各位粉丝们" ...

  7. redux源码分析之一:createStore.js

    欢迎关注redux源码分析系列文章: redux源码分析之一:createStore.js redux源码分析之二:combineReducers.js redux源码分析之三:bindActionC ...

  8. 深入理解redux之从redux源码到react-redux的原理

    在使用react的过程中,用redux来管理应用中的状态,使应用流更清晰的同时也会有小小的疑惑,比如reducer在redux中时怎么发挥作用的,为什么只要写好reducer,和dispatch特定a ...

  9. golangsha1解码_如何阅读Golang的源码?

    Go 的源码在安装包的 src/ 目录下.怎么看它的源码呢?直接看吧!没人教的情况下,只能自己撸了.当然,这种内容一般也不会有人教. 怎么撸? Go 源码中,应该可分为与语言息息相关的部分,和官方提供 ...

最新文章

  1. Airflow安装部署
  2. 《oracle大型数据库系统在AIX/unix上的实战详解》讨论31: oracle、sybase 数据库的不同访问...
  3. boost::mpi模块all_to_all() 集合的测试
  4. 心跳检测以及应用层心跳包机制设计
  5. vux 混合式开发
  6. bat产品经理能力模型_浅析产品经理能力模型
  7. 总结better-scroll插件的使用
  8. SDOI2018 旧试题
  9. window 软件 C盘 文件 搬家(配置文件搬家)
  10. python验证软件签名
  11. 【电脑配置】开发人员必备,黑苹果双系统安装教程
  12. R语言之K-mean聚类分析
  13. 搭建一个网站步骤 制作网页完整步骤
  14. activity工作流引擎
  15. 用网页服务器实现钢琴弹奏(使用Wizwiki-W7500)
  16. RuntimeError: Unable to open shape_predictor_68_face_landmarks.dat
  17. Physical CPU、Logical CPU、Core、Thread、Socket
  18. Facebook 疯狂投资 AR 技术!
  19. Clark与Park变换推导
  20. ups不间断电源高频机和工频机UPS不间断电源的比较

热门文章

  1. 打砖块小游戏php程序,利用原生js实现html5打砖块小游戏(代码示例)
  2. python中__init__.py的作用、module和package
  3. Python中什么是set
  4. [单选]物联网产业链的主要产品不包括下列哪一项 - 关于物联网(主讲:柳毅)笔记...
  5. docker 查看日志_8个优秀Docker容器监控工具,收藏了
  6. OpenCV copyMakeBorder()来拓展边界
  7. matlab 大括号
  8. 计算机基础知识:什么是位、字节、字、KB、MB
  9. UTF-8、GB2312、GB18030、GBK和BIG5等字符集编码范围的具体说明
  10. java 中map_Java中Map集合