前言

后面学习redux、redux-thunk的源码,接下来再接再厉,来理解一下react-redux的源码。

p.s.这里钻研的源码版本号是:7.2.4

作用

官网文档有这么一段话,翻译过去的意思是:redux是独立的库,它能够用于多种库或者框架中,咱们将它与某种UI库(框架)联合应用的时候,比起在UI库中操作redux,咱们往往会应用一种UI binding库去作为它们之间的桥梁。

Redux itself is a standalone library that can be used with any UI layer or framework, including React, Angular, Vue, Ember, and vanilla JS. Although Redux and React are commonly used together, they are independent of each other.

If you are using Redux with any kind of UI framework, you will normally use a "UI binding" library to tie Redux together with your UI framework, rather than directly interacting with the store from your UI code.

它的次要作用有:

  • react-redux还是redux官网团队保护的,当redux更新时,这个库也会跟着更新;
  • 进步了性能,通常状况下,当一个component产生扭转时,整棵树都会从新渲染一遍,然而react-redux外部帮咱们做了优化,进步了性能;
  • 社区弱小。

源码剖析

源码src/index.js文件中裸露进去的有:

export {  Provider,  connectAdvanced,  ReactReduxContext,  connect,  batch,  useDispatch,  createDispatchHook,  useSelector,  createSelectorHook,  useStore,  createStoreHook,  shallowEqual,}

ReactReduxContext

这个源码很简略,就是应用React.createContext创立了一个全局上下文。所以react-redux实质上就是借助context,须要借助Context给嵌套的组件传递store, 还须要当store中的state更新时,让嵌套的子组件的state也更新,所以前期会context中保留storedescription,简化了redux应用。

// src/components/Context.jsimport React from 'react'export const ReactReduxContext = /*#__PURE__*/ React.createContext(null)if (process.env.NODE_ENV !== 'production') {  ReactReduxContext.displayName = 'ReactRedux'}export default ReactReduxContext

batch

这是一个变量,跟react中的批量更新无关。批量更新,依照我现阶段的接触是这么了解的:若是间断调用N次更新,react不会每次都触发视图重渲染,而是会等更新都间接完取得最终后果的时候才会更新视图。

// src/utils/reactBatchedUpdatesexport { unstable_batchedUpdates } from 'react-dom'// src/index.jsimport { unstable_batchedUpdates as batch } from './utils/reactBatchedUpdates'

Provider

Provider的作用就是,你能够在父级组件中将store的状态注入,而后父组件包裹的任意组件都能够应用到store中的状态,而不必每个文件都要手动援用store

ReactDOM.render(  <Provider store={store}>    <App />  </Provider >,  document.getElementById('root'));

Provider是一个组件,那么根本构造跟一般的组件无异,依据文档可知,它承受三个参数:storechidren(个别是咱们的App组件)context,(如果这样提供了context的话,还须要为所有连贯的组件提供雷同的context)。

上文咱们晓得了react-redux次要是使用到了context,这里咱们不难剖析出Provider的根本架构:

import { ReactReduxContext } from './Context'function Provider({store, context, children}){    const Context = context || ReactReduxContext;    return (        <Context.Provider value={store}>{children}</Context.Provider>    )}

Provider中咱们须要对Contextstore来做一些解决,在此之前,咱们先来剖析几个小办法:

createListenerCollection

从函数名字来看,就是创立一个listener收集器,次要作用就是创立链表,每个节点有callback回调函数和双指针:前一个节点的指针(prev)和下一个节点的指针(next)。

createListenerCollection内申明一个first指向这个链表的头部,last指向这个链表的尾巴,还定义了以下几个办法:

  • clear: 革除firstlast指针,即清空链表;
  • notify:遍历listener中的每个节点,调用他们的callback。这里还应用到了下面提到的batch。猜想是遍历节点调用函数,batch能够确保函数都调用实现之后再一次性更新,达到批处理的作用,而不是每执行一次回调函数,就更新一次;
  • get:遍历整个链表,将每个节点顺次保留在listeners数组中,并返回;
  • subscribe: 新增监听的节点,先设置isSubscribed = true,表明曾经监听,而后像链表新增节点一样新增节点:

    • 创立一个新节点,含有callback、prev和next属性, 其中prev赋值为last(因为新节点会放在最初一个节点前面嘛);
    • 判断当初的链表是不是空的,如果是空的,那么这个节点就是头节点,否则,就将以后节点的上一个节点的next指向这个节点。

    最初还会返回一个勾销监听的函数,勾销监听这个节点:

    • 如果isSubscribedfalse或者first(链表)为空,就间接返回;
    • 设置isSubscribedfalse
    • 删除逻辑可能有点绕,咱们举个例子,以后节点是B:

      • 如果B有下一个节点C,那么咱们将Cprev指向B的上一个节点;
      • 否则,即B没有下一个节点,那么阐明此时last指向的是B,那么应该将last指向B的上一个节点;
      • 如果B有上一个节点A, 那么Anext应该指向B的下一个节点;
      • 否则,阐明B即在链表头,此时first指向的就是B,应该批改为B的下一个节点。
import { getBatch } from './batch'function createListenerCollection() {  // batch是批处理,react的更新机制跟batch无关,当有多个值一起更新时,会将他们合并起来一次更新,而不是每次更新一个值就从新渲染  const batch = getBatch()  let first = null  // 头指针  let last = null   // 尾指针, 阐明这个时双向链表  return {    // 清空指针,即链表清空    clear() {      first = null        last = null    },    notify() {      batch(() => {                  // 进行批处理调用了回调函数        let listener = first         // 从头遍历链表,执行每个回调函数        while (listener) {          listener.callback()          listener = listener.next        }      })    },    get() {      let listeners = []      let listener = first      while (listener) {        listeners.push(listener)        listener = listener.next      }      return listeners                //  返回所有订阅的回调函数    },    subscribe(callback) {      let isSubscribed = true        //  示意已订阅      let listener = (last = {       //  listener是一个节点,含有双向指针        callback,                  next: null,                  // 新增的节点,尾巴是空节点,所以是null        prev: last,                  // 新增节点的前一个节点,必定是上一个节点      })       if (listener.prev) {          // 如果新增的节点有前一个节点的话,那么它上一个节点要用next指针指向它        listener.prev.next = listener      } else {        // 没有的话,阐明新增的节点就算第一个节点        first = listener      }      return function unsubscribe() {        // 如果没有订阅,或者链表是空的,那么就不必勾销监听啦        if (!isSubscribed || first === null) return        // 勾销订阅        isSubscribed = false                // 因为删除了该节点,须要批改它的前后节点的指针指向        // 兴许还须要批改first和last指针        if (listener.next) {          listener.next.prev = listener.prev        } else {          last = listener.prev        }        if (listener.prev) {          listener.prev.next = listener.next        } else {          first = listener.next        }      }    },  }}

Subscription

Providerstore中的state传入到各个组件中并在state更新的时候同时让各个组件中的store也产生扭转,那么须要实时监听store并且反对解除绑定监听的操作。所以外部封装一个名为Subscription的函数,这个函数的意思是订阅。这个函数前期在其余办法实现中也会用到,所以须要理清它的思路。

encapsulates the subscription logic for connecting a component to the redux store, as well as nesting subscriptions of descendant components, so that we can ensure the ancestor components re-render before descendants

封装用于将组件连贯到redux存储的订阅逻辑,以及对后辈组件的嵌套订阅,以便咱们可能确保先人组件在后辈组件之前re-render

// src/utils/Subscription.jsconst nullListeners = { notify() {} }export default class Subscription {  constructor(store, parentSub) {    this.store = store              // redux中的store    this.parentSub = parentSub      // context.store    this.unsubscribe = null         // 订阅对象    this.listeners = nullListeners  // 订阅回调函数    this.handleChangeWrapper = this.handleChangeWrapper.bind(this)  }    // 给嵌套的组件减少监听  addNestedSub(listener) {    this.trySubscribe()    return this.listeners.subscribe(listener)  }      // 告诉嵌套组件state产生了扭转  notifyNestedSubs() {    this.listeners.notify()  }    // 当store扭转时执行的回调函数  handleChangeWrapper() {    if (this.onStateChange) {      this.onStateChange()    }  }    //  判断是否订阅了  isSubscribed() {    return Boolean(this.unsubscribe)  }      // 新增订阅  // 如果没有订阅,那么进行订阅,其中parentSub的优先级比store高  trySubscribe() {    if (!this.unsubscribe) {      this.unsubscribe = this.parentSub        ? this.parentSub.addNestedSub(this.handleChangeWrapper)        : this.store.subscribe(this.handleChangeWrapper)      // 初始化listeners, 创立链表      this.listeners = createListenerCollection()    }  }    // 勾销订阅  tryUnsubscribe() {    if (this.unsubscribe) {      this.unsubscribe()      this.unsubscribe = null      this.listeners.clear()      this.listeners = nullListeners    }  }}

综上所述,Subscription的作用就是传入store并创立一个listeners对象,来对store进行监听并告诉各个子组件,或者传入parentSub,新增监听节点,并遍历

useIsomorphicLayoutEffect

在钻研useIsomorphicLayoutEffect之前,咱们先来捋一下useEffectuseLayoutEffect的区别:

  • useEffect: 当render完结之后,callback函数会被执行,因为是异步的,所以不会阻塞浏览器的渲染;
  • useLayoutEffect: 如果你在useEffect中须要解决DOM操作,能够应用useLayoutEffect,不会呈现闪屏的状况。它会在DOM实现后立刻执行,然而会在浏览器进行任何绘制之前执行,所以会阻塞浏览器的渲染,是同步渲染;
  • 无论 useLayoutEffect还是 useEffect 都无奈在 Javascript 代码加载实现之前执行。在服务端渲染组件中引入 useLayoutEffect 代码时会触发 React 正告。解决这个问题,须要将代码逻辑移至 useEffect 中。

Provider

import { useEffect, useLayoutEffect } from 'react'export const useIsomorphicLayoutEffect =  typeof window !== 'undefined' &&  typeof window.document !== 'undefined' &&  typeof window.document.createElement !== 'undefined'    ? useLayoutEffect    : useEffect

为了解决下面的问题,浏览器渲染应用的是useLayoutEffect,服务器端渲染应用的是useEffect。应用useLayoutEffect是为了确保store的订阅回调函数的执行产生在最初一次的render commit,否则当store的更新产生在rendereffect之间时,咱们可能会失落更新。

咱们也须要确保subscription创立的时候是异步的,否则当store的更新产生在subscription的创立前,咱们察看到的state可能与store中的不统一。

源码

import React, { useMemo } from 'react'import PropTypes from 'prop-types'import { ReactReduxContext } from './Context'import Subscription from '../utils/Subscription'import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'function Provider({ store, context, children }) {  // useMemo会在依赖项产生扭转的时候,从新调用外部的函数  // 所以当store产生扭转的时候,会从新计算contextValue的值  const contextValue = useMemo(() => {    // 创立Subscription对象    const subscription = new Subscription(store)    // 将notifyNestedSubs绑定在onStateChange    subscription.onStateChange = subscription.notifyNestedSubs          // 敲重点,在react-redux中,contextValue的值都含有这两个属性    // subscription是一个监听对象,当store产生扭转时,能够通过subscription    // 给订阅了store的组件传递信息、也能够勾销监听    return {      store,      subscription,    }  }, [store])    // 当store产生扭转的时候,从新计算以后的getState的值  const previousState = useMemo(() => store.getState(), [store])    // 当previousState或者contextValue产生扭转的时候触发上面的逻辑  useIsomorphicLayoutEffect(() => {    const { subscription } = contextValue    // 遍历链表的每个节点,触发回调函数,给订阅了store的组件公布告诉    // 之后创立一个新的链表    subscription.trySubscribe()        // 可能公布告诉之后state的值产生扭转,    // 持续遍历链表,告诉    if (previousState !== store.getState()) {      subscription.notifyNestedSubs()    }          return () => {      // 勾销监听,便于垃圾回收机制回收      subscription.tryUnsubscribe()      subscription.onStateChange = null    }  }, [contextValue, previousState])    // 如果咱们没传递context,就默认应用ReactReduxContext  const Context = context || ReactReduxContext  return <Context.Provider value={contextValue}>{children}</Context.Provider>}export default Provider

从源码能够看进去Provider是一个函数,即能够充当组件,外部次要执行了几个步骤:

  • store创立了一个subscription对象,便于store更新的时候借助subscription进行告诉,并将两个赋值给ContextValue;
  • 应用Context.ProviderContextValue传递给Children
这里能够抛出一个疑难,如果react-redux中是借助context来传递store的话,那么如果同时要反对自定义的context的话,外部做了什么解决?

当在Provider中应用Context时候,先创立一个Conetxt,将其作为参数传入Provider中,然而肯定要在子组件中手动引入这个Context不然就会报错:

import React from 'react';export const ColorContext = React.createContext({color: 'red'});
// index.jsimport React from 'react';import ReactDOM from 'react-dom';import App from './App';import store from './store';import { Provider } from 'react-redux';import { ColorContext } from './store/context';ReactDOM.render(  <Provider store={store} context={ColorContext}>    <App />  </Provider >,  document.getElementById('root'));

为了防止谬误,在子组件中,context作为props传入:

// App.js<Count context={ColorContext} />

为了在外部读取到ColorContext,能够再ownProps中获取:

// Counter.jsfunction Counter(props){  const { count, increment, ColorContext } = props;  const contextValue = useContext(ColorContext);  console.log('contextValue123', contextValue);      return (    <>      <p>{count}</p>      <button onClick = { () => increment(1)}>+</button>    </>  )}function mapStateToProps(state, ownProps) {  return {     count: state.count,    ColorContext:  ownProps.ownProps  }}function mapDispatchToProps(dispatch) {  return { increment: bindActionCreators(actionCreators.increment, dispatch) }}export default connect(mapStateToProps, mapDispatchToProps)(Counter);

另外依据前面的源码可得, 这样也不会报错...,然而无奈在ownProps取得Context

 <Count store={store} />

connectAdvanced

这是一个素来没有用过的API!connect的底层是靠它实现的,不过官网文档说,将来可能会将它从代码中移除。connectAdvanced的跟connect的区别在于,并没有规定如何将state、props和dispatch传入到最终的props,也没有对产生的props做缓存来优化性能。

connectAdvanced接管两个参数:selectorFactoryfactoryOptions。在selectorFactory咱们能够实现将什么样的参数传入到组件中,这些参数能够依赖于store.getState()action和组件本身的props。官网DEMO:

function selectorFactory(dispatch) {  let ownProps = {}  let result = {}  const actions = bindActionCreators(actionCreators, dispatch)  const addTodo = (text) => actions.addTodo(ownProps.userId, text)  return (nextState, nextOwnProps) => {    const todos = nextState.todos[nextOwnProps.userId]    const nextResult = { ...nextOwnProps, todos, addTodo }    ownProps = nextOwnProps    if (!shallowEqual(result, nextResult)) result = nextResult    return result  }}export default connectAdvanced(selectorFactory)(TodoApp)

咱们权且将selectorFactory中返回的函数叫做selectorselector函数接管两个参数,新的state和组件本身的propsselector函数会在组件接管到新的state或者props时被调用,而后返回一个简略对象给被包裹的组件。

factoryOptions中的参数能够有这几种:

  • getDisplayName: 取得被connectAdvanced包裹后返回的新组件的名字,默认是ConnectAdvanced('+name+')
  • methodName: 用来显示在错误信息中的,默认是connectAdvanced;
  • shouldHandleStateChanges: 是否要跟踪state的变动,默认值是true
  • forwardRef: 当须要应用ref来保留被connectAdvanced包裹之后的新组件时,要设置为false;
  • contextcreateContext创立的context
  • 其余参数: 会被传递给selectorFactoryselector办法的第二个参数中。

这里上一下源码的根本架构,有些行将要被移除的参数被我删掉了!

export default function connectAdvanced(    selectorFactory,    {        getDisplayName = (name) => `ConnectAdvanced(${name})`,        methodName = 'connectAdvanced',        shouldHandleStateChanges = true,        forwardRef = false,        context = ReactReduxContext,        // 残余的参数会传递给selectorFactory        ...connectOptions    }){        // ...    return function wrapWithConnect(WrappedComponent) {        // ...        return hoistStatics(Connect, WrappedComponent)    }}

hoistStatics来源于hoist-non-react-statics这个包,它能够将你原先组件中的动态属性复制给connectAdvanced包裹后的新组件。举个栗子:

const Component.staticMethod = () => {}const WrapperComponent = enhance(Component);typeof WrapperComponent.staticMethod === undefined // true// 解决办法如下function enhance(Component) {  class WrapperComponent extends React.Component {/*...*/}  WrapperComponent.staticMethod = Component.staticMethod;  return Enhance;}// 过后若咱们不晓得原组件的所有动态属性值,下面的办法便有效了,// 所以能够借助hoist-non-react-staticsimport hoistNonReactStatic from 'hoist-non-react-statics';function enhance(Component) {  Class WrapperComponent extends React.Component {/* ... */}  hoistNonReactStatic(WrapperComponent, Component);  return WrapperComponent;}

持续剖析,来了解一下外部的每行代码:

export default function connectAdvanced(){    // 这里的前半部分,都对行将移除的属性做报错...        const Context = context;   // 保留要传递到组件外部的context    return function wrapWithConnect(WrappedComponent) {        // ...    }}

进入到wrapWithConnect源码:

function wrapWithConnect(WrappedComponent) {   // 先取得组件的名字    const wrappedComponentName =      WrappedComponent.displayName || WrappedComponent.name || 'Component'     // 给被包裹之后返回的新组件的起名字 (name) => `ConnectAdvanced(${name})`     const displayName = getDisplayName(wrappedComponentName)          const selectorFactoryOptions = {      ...connectOptions,      getDisplayName,      methodName,      shouldHandleStateChanges,      displayName,      wrappedComponentName,      WrappedComponent,    }         const { pure } = connectOptions          // 创立咱们的selector函数     function createChildSelector(store) {      return selectorFactory(store.dispatch, selectorFactoryOptions)    }          // 依据pure来判断是否用useMemo来进步被包裹返回的新组件的性能     const usePureOnlyMemo = pure ? useMemo : (callback) => callback()          function ConnectFunction(props){}          // 依据pure来判断是否用React.memo来进步被包裹返回的新组件的性能     const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction     Connect.WrappedComponent = WrappedComponent     Connect.displayName = ConnectFunction.displayName = displayName        // ...}

这里有一个ConnectFunction函数,源码巨长,持续剖析:

function ConnectFunction(props) {    // 从props中解构出了context、ref和残余的Props    const [        propsContext,        reactReduxForwardedRef,        wrapperProps,      ] = useMemo(() => {        const { reactReduxForwardedRef, ...wrapperProps } = props        return [props.context, reactReduxForwardedRef, wrapperProps]      }, [props])          // 之前有 context = ReactReduxContext; const Context = context     // Provider中用户能够传入自定义的Context,如果没有就默认是ReactReduxContext     // 用户如果之前在Provider传入context,须要在嵌套组件中手动传入该Context,故在这里咱们须要对Context的值进行判断,    // 应用适合的Context     const ContextToUse = useMemo(() => {        return propsContext &&          propsContext.Consumer &&          isContextConsumer(<propsContext.Consumer />)          ? propsContext          : Context      }, [propsContext, Context])          // 取得上下文中的值     const contextValue = useContext(ContextToUse)          // 后面讲Provider的时候提到过了     // Provider中封装了contextValue, 外部含有store和subscription     // 如果用户传了自定义的Context,嵌套组件必须传通过Context或者是store才不会报错。          // 这里要判断contextValue.store的起因就是     // 如果没有store,则须要从contextValue中找store,如果contextValue中也没有     // 就会报错     // 所以这就是为什么如果用户传递了context, 那么须要在嵌套组件中自定义传入context或者store的起因     const didStoreComeFromProps =        Boolean(props.store) &&        Boolean(props.store.getState) &&        Boolean(props.store.dispatch)      const didStoreComeFromContext =        Boolean(contextValue) && Boolean(contextValue.store)      if (        process.env.NODE_ENV !== 'production' &&        !didStoreComeFromProps &&        !didStoreComeFromContext      ) {        throw new Error(          `Could not find "store" in the context of ` +            `"${displayName}". Either wrap the root component in a <Provider>, ` +            `or pass a custom React context provider to <Provider> and the corresponding ` +            `React context consumer to ${displayName} in connect options.`        )      }        // 保留store    const store = didStoreComeFromProps ? props.store : contextValue.store        // 后面提到了,如果store产生扭转,那么会调用selector函数    const childPropsSelector = useMemo(() => {        return createChildSelector(store)      }, [store])        // ...}
const [subscription, notifyNestedSubs] = useMemo(() => {    // 如果不须要监听state的变动,则返回[null, null]    if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY    // 如果store是由context提供的话,须要对contextValue.subscription进行监听    const subscription = new Subscription(      store,      didStoreComeFromProps ? null : contextValue.subscription    )    // 复制notifyNestedSubs    const notifyNestedSubs = subscription.notifyNestedSubs.bind(      subscription    )    return [subscription, notifyNestedSubs]}, [store, didStoreComeFromProps, contextValue])// 笼罩以后的contextValueconst overriddenContextValue = useMemo(() => {    // 如果store是来自Props,那么间接返回contextValue    if (didStoreComeFromProps) {      return contextValue    }    // 否则,将该组件的subscription放入上下文中    return {      ...contextValue,      subscription,    }}, [didStoreComeFromProps, contextValue, subscription])
// 当store更新时,须要强制更新被被包裹的组件也进行更新,进而对子组件也进行更新const [    [previousStateUpdateResult],    forceComponentUpdateDispatch,] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)// 当产生谬误的时候会抛出异样,失常状况的话previousStateUpdateResult应该是nullif (previousStateUpdateResult && previousStateUpdateResult.error) {    throw previousStateUpdateResult.error}// 这里贴一下各个参数的源码function storeStateUpdatesReducer(state, action) {  const [, updateCount] = state  return [action.payload, updateCount + 1]}const EMPTY_ARRAY = []const initStateUpdates = () => [null, 0]
// 后面提到过,useIsomorphicLayoutEffect外部判断,// 如果是服务端返回useEffect// 如果是浏览器端返回useLayoutEffectimport { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'// 这里能够了解成,调用了useEffect/useLayoutEffect,不过参数都是动静的function useIsomorphicLayoutEffectWithArgs(  effectFunc,  effectArgs,  dependencies) {  useIsomorphicLayoutEffect(() => effectFunc(...effectArgs), dependencies)}// 持续剖析源码const lastChildProps = useRef()   // 更新之前传入以后组件的props(Component)const lastWrapperProps = useRef(wrapperProps) // 更新之前新组件的props (ConnectComponent)const childPropsFromStoreUpdate = useRef()     // 判断组件的更新是否是因为store的更新const renderIsScheduled = useRef(false)// usePureOnlyMemo是依据pure来判断是否应用memoconst actualChildProps = usePureOnlyMemo(() => {// 视图更新可能是因为store更新导致的会让组件    (Component)取得新的props// 如果Component有新的props,而ConnectComponent的props不变,那么应该应用新的props, 这样咱们能够取得ConnectComponent的新props// 然而如果咱们有了新的ConnectComponent props,它可能会扭转Component的props,那么就会从新进行计算// 为了避免出现问题,只有当ConnectComponent的props不变时,咱们才会依据Component的新props进行更新if (  childPropsFromStoreUpdate.current &&  wrapperProps === lastWrapperProps.current) {  return childPropsFromStoreUpdate.current}// 调用selector函数return childPropsSelector(store.getState(), wrapperProps)}, [store, previousStateUpdateResult, wrapperProps])      // 咱们须要上面的函数来执行同步到渲染,然而在SSR中应用useLayoutEffect会报错,这里咱们须要检测一下,如果是ssr,那么就改为应用effect来防止这个正告useIsomorphicLayoutEffectWithArgs(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  // 清空援用并进行更新  if (childPropsFromStoreUpdate.current) {    childPropsFromStoreUpdate.current = null    notifyNestedSubs()  }}// 当store或者subscription产生扭转时,咱们从新订阅useIsomorphicLayoutEffectWithArgs(    subscribeUpdates,    [      shouldHandleStateChanges,      store,      subscription,      childPropsSelector,      lastWrapperProps,      lastChildProps,      renderIsScheduled,      childPropsFromStoreUpdate,      notifyNestedSubs,      forceComponentUpdateDispatch,    ],[store, subscription, childPropsSelector])
function subscribeUpdates(  shouldHandleStateChanges, // 是否对store的更新进行订阅  store,                    // store  subscription,             // subscription  childPropsSelector,       // selector  lastWrapperProps,         // 上一次组件的props  lastChildProps,           // 上一次的子props  renderIsScheduled,        // 是否正在调度  childPropsFromStoreUpdate //判断子props是否是来自store的更新   notifyNestedSubs,         // notifyNestedSubs  forceComponentUpdateDispatch) {  // 如果没有订阅,间接返回  if (!shouldHandleStateChanges) return  // 捕捉值,并查看是否以及何时卸载该组件  let didUnsubscribe = false  let lastThrownError = null  // 当每次store更新流传到这个组件时,咱们就会调用这个函数  const checkForUpdates = () => {    if (didUnsubscribe) {      // 如果曾经勾销订阅,间接返回      return    }    // 取得更新前的store    const latestStoreState = store.getState()    let newChildProps, error    try {      // 调用selector来取得最新的子props      newChildProps = childPropsSelector(        latestStoreState,        lastWrapperProps.current      )    } catch (e) {      error = e      lastThrownError = e    }    if (!error) {      lastThrownError = null    }    // 如果新旧props一样,那么就不做任何解决    if (newChildProps === lastChildProps.current) {      if (!renderIsScheduled.current) {        // 调用subscription.notifyNestedSubs()        notifyNestedSubs()      }    } else {      // 用ref来存储最新的props,如果应用useState/useReducer来追踪,      // 那么将无奈确定这个值是否会加工过      // 也无奈清空这个值触发进行强制渲染      lastChildProps.current = newChildProps      childPropsFromStoreUpdate.current = newChildProps      renderIsScheduled.current = true      // 如果子props被更新了,那么能够从新渲染了      forceComponentUpdateDispatch({        type: 'STORE_UPDATED',        payload: {          error,        },      })    }  }  subscription.onStateChange = checkForUpdates  subscription.trySubscribe()  // 将store中产生扭转的值进行视图更新  checkForUpdates()  const unsubscribeWrapper = () => {    didUnsubscribe = true    subscription.tryUnsubscribe()    subscription.onStateChange = null    if (lastThrownError) {      // 当store更新,然而某个组件没有依赖store中更新的state切mapState办法有问题时      // 就会报错      throw lastThrownError    }  }  return unsubscribeWrapper}
// 回到次要源码中,咱们曾经解决完了须要传递的参数,接下来就是渲染组件// 还应用了useMemo来优化性能const renderedWrappedComponent = useMemo(() => (  <WrappedComponent    {...actualChildProps}    ref={reactReduxForwardedRef}  />),[reactReduxForwardedRef, WrappedComponent, actualChildProps])// 如果React看到与上次完全相同的元素援用,它会退出从新出现子元素,就像它被包装在React.memo()中或从shouldComponentUpdate返回false一样。const renderedChild = useMemo(() => {if (shouldHandleStateChanges) {  // 如果须要依据store的变动更新组件,那么就应用context.provider封装组件  // 并传入解决好的值  return (    <ContextToUse.Provider value={overriddenContextValue}>      {renderedWrappedComponent}    </ContextToUse.Provider>  )}// 否则间接返回原先的组件return renderedWrappedComponent}, [ContextToUse, renderedWrappedComponent, overriddenContextValue])return renderedChild

到这里connectAdvanced的源码就剖析结束了。咱们来捋一下思路,为了不便阐明我先写行代码:

function selector = (dispatch, ...args) => {}const connectComponent = connectAdvanced(selector, options)(component)
  • connectAdvanced是一个函数,接管一个selector办法和options对象:

    • selector办法是用户自定义的,接管一个dispacth参数,最初须要返回一个含有入参为nextState和nextOwnProps的函数,该外部函数最初会返回一个对象,对象的键值对就是行将传给component组件的props
    • options对象,能够传递官网指定的,也能够自定义,如果是自定义的,最初会被当做传入selector的第二个参数中。官网指定的参数中,有几个比拟重要,其中pure的值确定了最初返回的组件是否要进行性能优化;
    • connectAdvanced最初返回的是一个高阶函数,参数就是要包裹的组件。
  • 接下来对options中的值进行校验,并且对一些行将移除的属性做出正告解决;
  • 保留options中的context
  • 返回高阶函数wrapWithConnect(WrappedComponent),参数就是要包裹的组件,接下来就是剖析wrapWithConnect(WrappedComponent)中的内容了;
  • 先对传入的组件进行校验,判断是不是一个合格的组件;
  • 记录被包裹的组件component的名字,进而给返回的新组件connectComponent起名字;
  • options中多余的参数和定义好的参数封装起来,作为selector的参数;
  • 依据options的参数pure,用usePureOnlyMemo变量来判断是否应用useMemo办法;
  • 应用ConnectFunction创立一个Connect组件,还给这个组件设置了WrappedComponentdisplayName,其中这个ConnectFunctionconnectAdvanced中的外围:

    • ConnectFunction承受一个参数props, 这个props就是被包裹后的ConnetComponent的参数props
    • context、ref和残余的参数props中解耦进去;
    • 取得最终的context,并从中获得contextValue
    • 取得store;
    • 定义一个memo函数,当store的值产生扭转,就调用用户自定义的selector函数;
    • 取得最终的ContextValue的值;
    • 取得最终要返回被包裹的组件的actualChildProps
    • 当依赖的值产生扭转时,应用useEffect或者useLayoutEffect来调用captureWrapperProps,外面将以后的connentComponentpropsstorestate记录下来,不便下次渲染的时候比拟,如果store的值产生更新,调用notifyNestedSubs触发每个回调函数;
    • store或者subscription进行更新时,发动告诉,subscribeUpdates
    • 最初判断shouldHandleStateChanges,来决定是否在里面包裹Context,不须要则间接返回Connect组件。
  • 取得Connect组件后,通过判断forwardRef,如果存在,咱们须要应用React.forwardRef来转发Connect,否则间接应用Connect,还应用了hoistStatics来合并包裹前后的组件的动态属性。

小结

connectAdvanced最初的后果是返回一个Context.Comsumer,其中须要对最初传入组件的props参数进行解决,connectAdvancedconnect最大的区别在于,外部的pure属性默认是false,即它不会启动性能优化。

最初回到之前抛出的疑难:如果react-redux中是借助context来传递store的话,那么如果同时要反对自定义的context的话,外部做了什么解决?

react-redux通过Context来保留storesubscriptionsubscription用来进行订阅公布store的,当须要联合react-redux搭配应用context时,记得再嵌套组件中将同个名称的context当作props传入组件中,这样connectAdvanced会将用户自定义的Context当作props合并到最终组件的props中。

所以尽管react-redux反对你能够全局搞一个context然而没必要,很鸡肋~

shallowEqual

在钻研connect源码前先学习一下shallowEqual

===is(x,y)
NaN、NaNfalsetrue
0、+0truetrue
0、-0truefalse
+0、-0truefalse
// Object.is能够对根本数据类型:null,undefined,number,string,boolean做出十分准确的比拟,// 然而对于援用数据类型是没方法间接比拟的。function is(x, y) {  if (x === y) {    // 解决0、+0和-0的状况    return x !== 0 || y !== 0 || 1 / x === 1 / y  } else {    // 解决NaN的状况    return x !== x && y !== y  }}export default function shallowEqual(objA, objB) {  // 先用is来判断两个变量是否相等  if (is(objA, objB)) return true      if (    typeof objA !== 'object' ||    objA === null ||    typeof objB !== 'object' ||    objB === null  ) {    return false  }    // 遍历两个对象上的键值对,一次进行比拟  const keysA = Object.keys(objA)  const keysB = Object.keys(objB)  if (keysA.length !== keysB.length) return false  for (let i = 0; i < keysA.length; i++) {    if (      // Object.prototype.hasOwnProperty 和 in 运算符不同,该办法会疏忽掉那些从原型链上继承到的属性。      !Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||      !is(objA[keysA[i]], objB[keysA[i]])    ) {      return false    }  }  return true}

一开始先比拟,调用了is来判断两个值是否统一,这里兼容了一些常见的根本来信===判断有问题的状况;之后判断是否不是对象或者是null,如果满足就返回false, 否则就遍历两个对象的key进行比拟。从源码能够看出浅比拟是不适用于嵌套类型的比拟的

以上就是浅比拟的思维啦~

从MDN能够看出isObject.is()的一种Polyfill实现。

connect

看源码的时候顺便去看了官网文档,第一个感觉是我的英语真的提高了!第二个感觉是我以前是不是没翻过,怎么看到了很多忽略的中央??!

根本架构

// src/connect/connect.jsimport connectAdvanced from '../components/connectAdvanced'import shallowEqual from '../utils/shallowEqual'import defaultMapDispatchToPropsFactories from './mapDispatchToProps'import defaultMapStateToPropsFactories from './mapStateToProps'import defaultMergePropsFactories from './mergeProps'import defaultSelectorFactory from './selectorFactory'export function createConnect({  onnectHOC = connectAdvanced,  mapStateToPropsFactories = defaultMapStateToPropsFactories,  mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,  mergePropsFactories = defaultMergePropsFactories,  selectorFactory = defaultSelectorFactory,} = {}){   return function connect(       mapStateToProps,    mapDispatchToProps,    mergeProps,    {      pure = true,      areStatesEqual = strictEqual,      areOwnPropsEqual = shallowEqual,      areStatePropsEqual = shallowEqual,      areMergedPropsEqual = shallowEqual,      ...extraOptions    } = {}   ){       // ...       return connectHOC()   } }export default /*#__PURE__*/ createConnect()

导出前执行了createConnect办法,最初返回的是一个咱们意识的connect函数,外部调用了HOC函数。这外面波及到了秘稀稀拉拉的名字类似的参数,看的有点脑壳疼。

调用createConnect()时未传递参数,那么外面的参数都是读取默认值:

connectHOC = connectAdvanced,mapStateToPropsFactories = defaultMapStateToPropsFactories,mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,mergePropsFactories = defaultMergePropsFactories,selectorFactory = defaultSelectorFactory,

而后返回了咱们属性的connect函数,它反对四个参数:mapStateToProps、mapDispatch、mergeProps和一个对象。由官网文档可知:

  • mapStateToProps: 函数类型,能够传入store.getState()和以后组件本身的props,最初须要返回一个对象;
  • mapDispacthToProps:函数类型,接管store.dispacth和以后组件本身的props,最初须要返回一个对象;
  • mergeProps: 函数类型,接管store.getState()store.dispacth和组件本身的props,最初须要返回对象;
  • options: 这个对象外面有几个值:

    {  context?: Object,    // 当咱们在Provider传入context时,在子组件中引入,就能够写在这里  pure?: boolean,      // 为了进步react-redux的性能,默认开启pure,相似pureComponent,只有在组件本身的props、mapStateToProps的后果、或者是mapDispatchToProps的后果扭转时才会从新渲染组件  areStatesEqual?: Function,   // 开启pure时,传递进来的state更新时的比拟函数  areOwnPropsEqual?: Function, // 开启pure时,本身props更新时的比拟函数  areStatePropsEqual?: Function,  // 开启pure时,mapStateToProps更新时的比拟函数  areMergedPropsEqual?: Function, // 开启pure时,mergeProps的返回值更新时的比拟函数  forwardRef?: boolean,   // 如果组件须要接管ref,须要在这里设置为true}

最初返回一个高阶函数,connectHOC就是咱们之前介绍的connectAdvanced,之前说的connectAdvanced接管两个参数,一个是selector函数,一个是options对象:

return connectHOC(selectorFactory, {    // 为了在报错的时候应用    methodName: 'connect',    // 依据被connect包裹的组件的名字,给返回的新组件起名字    getDisplayName: (name) => `Connect(${name})`,    // 如果mapStateToProps为Null,那么就不须要监听更新state啦    shouldHandleStateChanges: Boolean(mapStateToProps),    // 上面这些值是给selectorFactory做为参数的    // 之前说的connectAdvanced的第二个参数options是个对象    // 外面除了规定的key,其余属性最终都会成为selector函数的第二个参数    initMapStateToProps,    initMapDispatchToProps,    initMergeProps,    pure,    areStatesEqual,    areOwnPropsEqual,    areStatePropsEqual,    areMergedPropsEqual,    // any extra options args can override defaults of connect or connectAdvanced    ...extraOptions,  })

逐渐认识一下createConnect中的默认参数:

connectHoc

它的默认值是connectAdvanced,这个下面曾经理解过了,就不反复。

defaultMapStateToPropsFactories

在钻研之前来看一下这个源码中引入的文件wrapMapToProps

import verifyPlainObject from '../utils/verifyPlainObject'export function wrapMapToPropsConstant(getConstant) {  return function initConstantSelector(dispatch, options) {    const constant = getConstant(dispatch, options)    function constantSelector() {      return constant    }    // dependsOnOwnProps是判断以后的mapStateToProps的值,有没有依赖于ownProps    constantSelector.dependsOnOwnProps = false    return constantSelector  }}// 有没有依赖于ownPropsexport function getDependsOnOwnProps(mapToProps) {  return mapToProps.dependsOnOwnProps !== null &&    mapToProps.dependsOnOwnProps !== undefined    ? Boolean(mapToProps.dependsOnOwnProps)    : mapToProps.length !== 1}// 封装一个mapToProps代理函数// 检测selectorFactory是否依据ownProps来更新。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)    }    // 容许detectFactoryAndVerify失去本人的props    proxy.dependsOnOwnProps = true    proxy.mapToProps = function detectFactoryAndVerify(      stateOrDispatch,      ownProps    ) {      proxy.mapToProps = mapToProps      proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)      let props = proxy(stateOrDispatch, ownProps)            // props返回函数,则解决mapToProps,并将该新函数作为真正的mapToProps用于后续调用      if (typeof props === 'function') {        proxy.mapToProps = props        proxy.dependsOnOwnProps = getDependsOnOwnProps(props)        props = proxy(stateOrDispatch, ownProps)      }      // 如果不是一个一般对象,就收回正告      if (process.env.NODE_ENV !== 'production')        verifyPlainObject(props, displayName, methodName)      return props    }    return proxy  }}
// src/connect/mapStateToProps.jsimport { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'export function whenMapStateToPropsIsFunction(mapStateToProps) {  return typeof mapStateToProps === 'function'    ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')    : undefined}export function whenMapStateToPropsIsMissing(mapStateToProps) {  return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined}export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]

mapStateToProps返回一个数组,别离是whenMapStateToPropsIsFunctionwhenMapStateToPropsIsMissing,从函数名字可得,当mapStateToProps是函数或者是空的时候,别离调用的这两个函数。

defaultMapDispatchToPropsFactories

// src/connect/mapDispatchToProps.jsimport bindActionCreators from '../utils/bindActionCreators'import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {  return typeof mapDispatchToProps === 'function'    ? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps')    : undefined}export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {  return !mapDispatchToProps    ? wrapMapToPropsConstant((dispatch) => ({ dispatch }))    : undefined}export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {  return mapDispatchToProps && typeof mapDispatchToProps === 'object'    ? wrapMapToPropsConstant((dispatch) =>        bindActionCreators(mapDispatchToProps, dispatch)      )    : undefined}export default [  whenMapDispatchToPropsIsFunction,  whenMapDispatchToPropsIsMissing,  whenMapDispatchToPropsIsObject,]

mapDispatchToProps返回一个数组,别离是whenMapDispatchToPropsIsFunctionwhenMapDispatchToPropsIsMissingwhenMapDispatchToPropsIsObject,从函数名字可得,当mapDispatchToProps是函数、空或者是对象的时候的时候,别离调用的这三个函数。

defaultMergePropsFactories

// src/connect/mergeProps.jsimport verifyPlainObject from '../utils/verifyPlainObject'export function defaultMergeProps(stateProps, dispatchProps, ownProps) {  return { ...ownProps, ...stateProps, ...dispatchProps }}export function wrapMergePropsFunc(mergeProps) {  return function initMergePropsProxy(    dispatch,    { displayName, pure, areMergedPropsEqual }  ) {    let hasRunOnce = false    let mergedProps    return function mergePropsProxy(stateProps, dispatchProps, ownProps) {      const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps)      if (hasRunOnce) {        if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps))          mergedProps = nextMergedProps      } else {        hasRunOnce = true        mergedProps = nextMergedProps        if (process.env.NODE_ENV !== 'production')          verifyPlainObject(mergedProps, displayName, 'mergeProps')      }      return mergedProps    }  }}export function whenMergePropsIsFunction(mergeProps) {  return typeof mergeProps === 'function'    ? wrapMergePropsFunc(mergeProps)    : undefined}export function whenMergePropsIsOmitted(mergeProps) {  return !mergeProps ? () => defaultMergeProps : undefined}export default [whenMergePropsIsFunction, whenMergePropsIsOmitted]

mergeProps返回一个数组,别离是whenMergePropsIsFunctionwhenMergePropsIsOmitted,从函数名字可得,当mergeProps是函数或者是省略了的时候,别离调用的这两个函数。

defaultSelectorFactory

这个函数的名字翻译过去是: 抉择工厂函数,就生成connectAdvanced须要的selector函数,selector的第一个参数是dispatch, 第二个参数是对象,来瞄一下源码:

// import defaultSelectorFactory from './selectorFactory'// selectorFactory.jsexport default function finalPropsSelectorFactory(  dispatch,  { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }) {  // 先初始化值  const mapStateToProps = initMapStateToProps(dispatch, options)  const mapDispatchToProps = initMapDispatchToProps(dispatch, options)  const mergeProps = initMergeProps(dispatch, options)  if (process.env.NODE_ENV !== 'production') {    // ...  }      // 依据pure应用不同的函数,并调用它们  const selectorFactory = options.pure    ? pureFinalPropsSelectorFactory    : impureFinalPropsSelectorFactory  return selectorFactory(    mapStateToProps,    mapDispatchToProps,    mergeProps,    dispatch,    options  )}
impureFinalPropsSelectorFactory

如果咱们没有开启pure的话,那么不做什么骚操作,间接返回mergeProps,输入最初要传递给包裹后的组件的props

export function impureFinalPropsSelectorFactory(  mapStateToProps,  mapDispatchToProps,  mergeProps,  dispatch) {  return function impureFinalPropsSelector(state, ownProps) {    return mergeProps(      mapStateToProps(state, ownProps),      mapDispatchToProps(dispatch, ownProps),      ownProps    )  }}
pureFinalPropsSelectorFactory

当第一次执行这个函数的时候,会应用存储这次的state、props,再通过这两个值,去取得最初传给包裹组件的stateprops合并后的props

如果曾经执行过的话,调用handleSubsequentCalls来比照此次更新是只更新state、只更新props还是stateprops都更新,进而调用不同的逻辑,最初给包裹的组件最终的props

敲黑板了,上面的逻辑跟react-redux的性能无关。咱们在应用connect是能够传入mapStateToProps、mapDispatchtoProps、mergeProps和Options四个参数,其中前两个是必传的。

  • mapStateToProps能够传入两个参数,stateownProps,最初返回一个新的state,所以咱们能够晓得新的state不仅取决于store的变动,也取决于ownProps的变动,他们两个只有更新一个那么就会从新计算state
  • mapDispatchtoProps也反对传入两个参数,dispatchownProps,返回一个新的dispatch,新的dispatch的更新取决于dispacthownProps
  • mergeProps是一个函数,默认状况下咱们都会省略,所以会被默认赋值,最初将新的state、新的dispacthownProps变为新的props传递给Connect组件。

所以不论是store的更新还是组件本身props的更新,都会影响到被connect返回的新组件,这种性能开销是很大的,所以react-redux做了性能优化。

connectconnectAdvanced多了一个pure的参数,默认是true。是用来开启性能优化的。

export function pureFinalPropsSelectorFactory(  mapStateToProps,  mapDispatchToProps,  mergeProps,  dispatch,  { areStatesEqual, areOwnPropsEqual, areStatePropsEqual }) {  let hasRunAtLeastOnce = false  let state  let ownProps  let stateProps  let dispatchProps  let mergedProps      // 首次执行,依据以后的state和props,取得最终的stateProps和dispatchProps  // 进而同个mergedProps取得最终传入组件props  function handleFirstCall(firstState, firstOwnProps) {    state = firstState    ownProps = firstOwnProps    stateProps = mapStateToProps(state, ownProps)    dispatchProps = mapDispatchToProps(dispatch, ownProps)    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)    hasRunAtLeastOnce = true    return mergedProps  }    // 如果本身的Props和store都扭转,调用这个函数  function handleNewPropsAndNewState() {    // 那么间接通过mapStateToProps取得新的stateProps    stateProps = mapStateToProps(state, ownProps)    // mapDispatchToProps办法如果有依赖ownProps,那么也须要从新取得新的dispatchProps    if (mapDispatchToProps.dependsOnOwnProps)      dispatchProps = mapDispatchToProps(dispatch, ownProps)    // 传递新的stateProps、dispatchProps,通过mergedProps取得最终传入组件的props    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)    return mergedProps  }      // 如果只有本身的props扭转  function handleNewProps() {    // 申明mapStateToProps时如果应用了ownProps参数同样会产生新的stateProps!    if (mapStateToProps.dependsOnOwnProps)      stateProps = mapStateToProps(state, ownProps)        // 申明mapDispatchToProps时如果应用了第二个参数(ownProps)这里会标记为true    if (mapDispatchToProps.dependsOnOwnProps)      dispatchProps = mapDispatchToProps(dispatch, ownProps)    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)    return mergedProps  }   // 只有store扭转  function handleNewState() {    // 当store扭转,那么mapStateToProps会返回新的值    const nextStateProps = mapStateToProps(state, ownProps)    // 判断新的stateProps和上一次的stateProps是否相等    const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)    stateProps = nextStateProps         // 如果扭转,才须要从新计算最终的props    if (statePropsChanged)      mergedProps = mergeProps(stateProps, dispatchProps, ownProps)    return mergedProps  }   // 如果不是第一次执行,  function handleSubsequentCalls(nextState, nextOwnProps) {    // 判断组件本身的Props是否扭转    const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)    // 判断store是否扭转    const stateChanged = !areStatesEqual(nextState, state)    // 记录下以后的state和ownProps,不便下次更新比拟    state = nextState    ownProps = nextOwnProps    if (propsChanged && stateChanged) return handleNewPropsAndNewState()    if (propsChanged) return handleNewProps()    if (stateChanged) return handleNewState()    return mergedProps  }  return function pureFinalPropsSelector(nextState, nextOwnProps) {    // 判断是否执行过一次,来调用handleSubsequentCalls或者handleFirstCall    return hasRunAtLeastOnce      ? handleSubsequentCalls(nextState, nextOwnProps)      : handleFirstCall(nextState, nextOwnProps)  }}

捋分明selectorFactory,接下来就是connectAdvanced的执行流程了,就不复述了。

Hook

最初来聊一聊新增的Hook,6月份我的项目还在啃老本,明明我的项目用的是hook,然而对于react-redux却齐全不晓得有hook相干的api存在,持续学习:

useSelector

useSelector有点相似于mapStateToProps,能够从store中获得咱们想要的state,然而不同的是mapStateToProps用的是浅比拟,而useSelector用的是深比拟,只有state产生扭转,就必定会触发从新渲染,所以个别状况下,useSelector只返回一个值,如果想要返回一个对象,倡议搭配shallowEqual进行应用,或者借助reselect创立一个记忆选择器,该选择器在一个对象中返回多个值,但只在其中一个值发生变化时返回一个新对象。

import { shallowEqual, useSelector } from 'react-redux'import { createSelector } from 'reselect'// 惯例用法export const CounterComponent = () => {  const counter = useSelector((state) => state.counter)  return <div>{counter}</div>}// 应用浅层比拟,取得一个对象export const ObjComponent = () => {  const obj = useSelector((state) => state.obj, shallowEqual)  return <div>{obj.counter}</div>}// 借助reselectconst selectNumCompletedTodos = createSelector(  (state) => state.todos,  (todos) => todos.filter((todo) => todo.completed).length)export const CompletedTodosCounter = () => {  const numCompletedTodos = useSelector(selectNumCompletedTodos)  return <div>{numCompletedTodos}</div>}export const App = () => {  return (    <>      <span>Number of completed todos:</span>      <CompletedTodosCounter />    </>  )}

接下来开始剖析源码:

// useSelector是由createSelectorHook返回的export const useSelector = /*#__PURE__*/ createSelectorHook()

所以能够揣测出

createSelectorHook() = (selector, equalityFn) => selectedState

来看看createSelectorHook:

export function createSelectorHook(context = ReactReduxContext) {  // 后面createSelectorHook()没有传递参数,所以context = ReactReduxContext  const useReduxContext =    // 取得context    context === ReactReduxContext      ? useDefaultReduxContext      : () => useContext(context)    // 返回一个(selector, equalityFn = refEquality) => selectorState的函数给useSelector  // const refEquality = (a, b) => a === b useSelector默认是严格比拟  return function useSelector(selector, equalityFn = refEquality) {    // 对selector, equalityFn进行校验    if (process.env.NODE_ENV !== 'production') {      if (!selector) {        throw new Error(`You must pass a selector to useSelector`)      }      if (typeof selector !== 'function') {        throw new Error(`You must pass a function as a selector to useSelector`)      }      if (typeof equalityFn !== 'function') {        throw new Error(          `You must pass a function as an equality function to useSelector`        )      }    }        // 后面咱们看源码的时候晓得了,context会存储store和subscription    const { store, subscription: contextSub } = useReduxContext()        // 取得selectedState并返回    const selectedState = useSelectorWithStoreAndSubscription(      selector,      equalityFn,      store,      contextSub    )    useDebugValue(selectedState)    return selectedState  }}

来看看useSelectorWithStoreAndSubscription是如何返回咱们想要的selectState:

function useSelectorWithStoreAndSubscription(  selector,  equalityFn,  store,  contextSub    // subscription) {  const [, forceRender] = useReducer((s) => s + 1, 0)    // 创立一个新的订阅对象  const subscription = useMemo(() => new Subscription(store, contextSub), [    store,    contextSub,  ])    const latestSubscriptionCallbackError = useRef()  const latestSelector = useRef()  const latestStoreState = useRef()  const latestSelectedState = useRef()    // 取得当初的store中的state  const storeState = store.getState()  let selectedState  try {    // 如果selector扭转了、或者store中的state扭转了、或者呈现谬误了    // 那么就从新执行selector取得新的selectState    // 否则间接返回上一次的selectState    if (      selector !== latestSelector.current ||      storeState !== latestStoreState.current ||      latestSubscriptionCallbackError.current    ) {      // 将new selectState 和 last selectState比拟      // 如果统一,返回上一次的selectState      // 否则,返回new selectState      const newSelectedState = selector(storeState)      if (        latestSelectedState.current === undefined ||        !equalityFn(newSelectedState, latestSelectedState.current)      ) {        selectedState = newSelectedState      } else {        selectedState = latestSelectedState.current      }    } else {      selectedState = latestSelectedState.current    }  } catch (err) {    if (latestSubscriptionCallbackError.current) {      err.message += `\nThe error may be correlated with this previous error:\n${latestSubscriptionCallbackError.current.stack}\n\n`    }    throw err  }    // 这个函数每次都被执行的时候,会保留这次的selector、store中的state、selectState和订阅捕捉谬误  // 便于下次比拟做应用  useIsomorphicLayoutEffect(() => {    latestSelector.current = selector    latestStoreState.current = storeState    latestSelectedState.current = selectedState    latestSubscriptionCallbackError.current = undefined  })    // 当store或者subscription产生扭转时  // 会调用checkForUpdates取得新的storestate和selectState  // 比拟这次和上次的selectState的值  // 如果不等,更新latestSelectedState和latestStoreState、latestSubscriptionCallbackError  useIsomorphicLayoutEffect(() => {    function checkForUpdates() {      try {        const newStoreState = store.getState()        const newSelectedState = latestSelector.current(newStoreState)        if (equalityFn(newSelectedState, latestSelectedState.current)) {          return        }        latestSelectedState.current = newSelectedState        latestStoreState.current = newStoreState      } catch (err) {        // we ignore all errors here, since when the component        // is re-rendered, the selectors are called again, and        // will throw again, if neither props nor store state        // changed        latestSubscriptionCallbackError.current = err      }      forceRender()    }    subscription.onStateChange = checkForUpdates    subscription.trySubscribe()    checkForUpdates()    return () => subscription.tryUnsubscribe()  }, [store, subscription])    // 最初返回咱们须要的selectedState  return selectedState}

useSelector外部次要通过useSelectorWithStoreAndSubscription来取得咱们想要的selectStateuseSelectorWithStoreAndSubscription通过比拟selectorstore判断是否要执行selector来取得最新的selectState,而后将新获取到了selectState与上一次selectState来做比拟,判断返回哪一个值。外部为了不便比拟,都会记录下以后的selector、store、selectState、和捕捉的谬误,目标就是、为了进步useSelector的性能,不必每次数据明明跟之前一样却要返回新的内容导致视图更新。

useDispatch

useDiapatch的实质就是取得store中的dispatch。举一个官网栗子:

import React, { useCallback } from 'react'import { useDispatch } from 'react-redux'export const CounterComponent = ({ value }) => {const dispatch = useDispatch() const increaseCounter = useCallback(() => dispatch({ type: 'increase-counter' }), [])  return (   <div>     <span>{value}</span>      <button onClick={increaseCounter}>Increase counter</button>   </div> )}

间接看源码:

export function createDispatchHook(context = ReactReduxContext) {  const useStore =    context === ReactReduxContext ? useDefaultStore : createStoreHook(context)  return function useDispatch() {    const store = useStore()    return store.dispatch  }}export const useDispatch = /*#__PURE__*/ createDispatchHook()

不难理解,先取得Context,再取得Context中的store,最初返回store.dispatch

useStore

useDispatch中,有一个代码是createStoreHook(context),其实createStoreHook就是useStore的实现:

export function createStoreHook(context = ReactReduxContext) {  const useReduxContext =    context === ReactReduxContext      ? useDefaultReduxContext      : () => useContext(context)  return function useStore() {    const { store } = useReduxContext()    return store  }}export const useStore = /*#__PURE__*/ createStoreHook()

先取得Context,再取得Context中的store,最初将store返回~

小结

源码断断续续看了一周,还来回看了几遍,有点绕。官网尽管说在不久会废除connectAdvanced,然而为了connect的实现,仍旧得好好啃啊。通过学习这次源码也对useMemo这个钩子加深了印象,晓得了mapStateToProps和mapDispacthToProps能够将ownProps当作第二个参数传入对性能的影响,最初就是学习到了外部新带hook,还接触到了reselect这个库。

最初,肯定要多翻翻英文文档啊~

参考

  • react-redux官网
  • 庖丁解牛React-Redux(一): connectAdvanced
  • Polyfill
  • 你真的理解浅比拟么?

如有谬误,欢送指出,感激浏览~