react-redux 源码浅析

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

redux 是一个库,但更是一种思想, 而 react-redux 就是一座桥了, 他链接了两中模式, 如今让咱们一探究竟github

分模块

咱们将 redux 使用的流程分红 3 个模块redux

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

前置知识点

想要理解此中源码首先就须要理解不少 react hooks 的知识点 还有熟练使用 redux 的经验, 这里我就先简介一下设计模式

Subscription

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

hooks

关于 hooks 中 咱们须要了解到的知识点:缓存

  • useMemo
    缓存代码的过程, 若是依赖不变则, 直接返回结果
  • useContext
    在函数中使用 context 的方案
  • useRef
    最开始是用来获取 ref 的, 后面也用来存储变量
  • useReducer
    建立一个小的 reducer, 固然也有他本身的 state 和 dispatch

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

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使用 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

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

相关文章
相关标签/搜索