react-redux 版本号 7.2.3

react-redux 依赖的库:

"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 的源码下载了下来,所以这些依赖就必须手动装置了

留神: 对于 hooks 的解析会放到下一文

redux

redux 是一个库,但更是一种思维, 而 react-redux 就是一座桥了, 他连贯了两中模式, 当初让咱们一探到底

分模块

咱们将 redux 应用的流程分成 3 个模块

  1. store 的创立
  2. provider 提供数据的注入
  3. connect 在想要的组件中应用

前置知识点

想要了解此中源码首先就须要了解很多 react hooks 的知识点 还有纯熟应用 redux 的教训, 这里我就先简介一下

Subscription

咱们要先了解一个设计模式 - 订阅公布模式 他位于文件: react-redux/src/utils/Subscription.js 具体的代码咱们会在前面细说

hooks

对于 hooks 中 咱们须要理解到的知识点:

  • useMemo
    缓存代码的过程, 如果依赖不变则, 间接返回后果
  • useContext
    在函数中应用 context 的计划
  • useRef
    最开始是用来获取 ref 的, 前面也用来存储变量
  • useReducer
    创立一个小的 reducer, 当然也有他本人的 state 和 dispatch

具体的知识点还须要去官网理解: https://zh-hans.reactjs.org/d...

store

对于 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

provider 的应用

一般来说, 咱们会在我的项目入口处加上 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 了, 当初须要晓得的两点:

  1. 通过 subscription.addNestedSub(listener) 函数, 增加监听事件
  2. 通过 subscription.notifyNestedSubs(), 触发之前所有的监听事件
  3. subscription.trySubscribe() 属于第一点中函数的子函数, 成果进去不能增加回调以外,相似
  4. subscription.tryUnsubscribe(), 与第三点相同, 解除监听

当初, 咱们列举下 Provider 做了什么事件:

  1. 创立了 context 须要传递的值
  2. 记录之前 store 的值
  3. 以后 store 值和之前记录的不一样时, 触发监听事件

connect

真正的重头戏来了, 将 redux 的 store 与任意的组件连贯

connect 的应用

connect 参数

在这里咱们首先须要晓得的是 connect , 通过他是怎么应用的, 倒推回去看源码会更有帮忙 他的定义:

function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)

能够看到connect 可承受 4 个参数

  1. mapStateToProps:
mapStateToProps?: (state, ownProps?) => Object

他是一个函数, 承受 state 和 ownProps 两个参数, 返回一个对象, 如果 mapStateToProps 传递的是一个函数, 那么 store 更新的时候,包装的组件也会订阅更新 如果传递 undefined 或者
null, 能够防止不须要的更新

对于 ownProps 的用法, ownProps 其实就是组件的 props

const mapStateToProps = (state, ownProps) => ({  todo: state.todos[ownProps.id],})
  1. mapDispatchToProps
mapDispatchToProps?: Object | (dispatch, ownProps?) => Object

第二个参数, 能够是函数, 能够是对象, 也能够是空值 如果是函数, 则能够收取到两个参数, dispatchownProps
通常咱们是这样做的:

const mapDispatchToProps = (dispatch) => {return {    increment: () => dispatch({ type: 'INCREMENT' }),    decrement: () => dispatch({ type: 'DECREMENT' }),  }}

ownProps 的用法和 mapStateToProps 雷同 以后参数如果是一个对象的时候,
须要管制外面的属性都是 action-creator
在源码中将会调用: bindActionCreators(mapDispatchToProps, dispatch) 来生成可用代码
官网中的简介: 点击查看

  1. mergeProps
mergeProps?: (stateProps, dispatchProps, ownProps) => Object

这个参数的作用就是, 以后 connect 包装的组件, 对于他的 props 再次自定义 ,如不传递这个属性, 则代码中默认传递值为: { ...ownProps, ...stateProps, ...dispatchProps }

  1. options
options?: Object

Object 中的内容:

{  context?: Object,  pure?: boolean,  areStatesEqual?: Function,  areOwnPropsEqual?: Function,  areStatePropsEqual?: Function,  areMergedPropsEqual?: Function,  forwardRef?: boolean,}

只有版本再 >=6.0 的时候才会有这个属性, 都是配置性的属性, 一般来说默认值就能应酬 99% 的状况了
更加具体的作用能够在此处点击查看: 点击查看

connect 返回后果:

这是一个一般的用法:

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() 中执行了什么:

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

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

这里要说下 hoistStatics , 他来自于 hoist-non-react-statics 这个库
简略的来说能够看成 Object.assign, 然而他是组件级别的

ConnectFunction

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

captureWrapperProps

代码是在这里:

 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()    }}

subscribeUpdates

源码:

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 的变动都会导致此包装组件的再渲染, 选渲染中又加上了判断, 能够管制子组件是否真的可能渲染

补漏

selectorFactory

这函数是获取 store 的, 之前应用的中央

// childPropsSelector应用 1newChildProps = childPropsSelector(        latestStoreState,        lastWrapperProps.current)// childPropsSelector应用 2childPropsSelector(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

先说说 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

defaultMapStateToPropsFactories

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 执行:

执行的是 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  })}

所以说查看源码, 是能够发现一些新的用法的, 尽管这样的写法不是很提倡, 临时没什么作用, 但为前面他的拓展性做了很短缺的筹备

defaultMapDispatchToPropsFactories

这个参数是和 defaultMapStateToPropsFactories 相似了
而且因为 mapDispatchToProps 能够传入 Object, 在 match 上又会多一层判断
这里波及到的代码和 defaultMapStateToPropsFactories 90% 都是相似了, 所以跳过

defaultMergePropsFactories

同上, 根本相似, 也不再赘述

selectorFactory

到这里持续讲 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 这个罕用库, 总结了几种代码运行流程

  1. store 数据流向
    store -> Provider -> connect -> props
  2. connect 执行函数流程
    createConnect() -> connect() -> connectHOC() = connectAdvanced() -> wrapWithConnect(Component)
  3. mapStateToProps执行流程
    咱们传递的 mapStateToProps ->
    通过 match 函数 ->
    match 函数中的 wrapMapToPropsFunc ->
    当初执行的是 initProxySelector ->
    别名 initMapStateToProps ->
    通过执行他 取得后果 const mapStateToProps = initMapStateToProps(dispatch, options) ->
    通过 finalPropsSelectorFactory 的包装 ->
    别名 selectorFactory ->
    在函数中杯执行 selectorFactory(store.dispatch, selectorFactoryOptions) ->
    返回的值, 作为 childPropsSelector 的值 ->
    在新旧 props 比拟时(subscribeUpdates中)使对此这个值

本文记录:
https://github.com/Grewer/rea...

参考文档:
https://react-redux.js.org/in...
https://github.com/reduxjs/re...