redux && react-redux 源码解读

612 阅读12分钟

本文解读redux版本^4.0.5,react-redux版本^7.2.1,代码仓库为从redux官方demo fork

初始化store

首先通过如下语句引入store对象

import store from './store'

那么store对象又是通过createStore方法创建的,参数为rootReducer和composedEnhancer,接下来分别具体讲一下这两个参数和具体createStore做的事情

const composedEnhancer = composeWithDevTools(
  applyMiddleware(thunkMiddleware)
  // other store enhancers if any
)

const store = createStore(rootReducer, composedEnhancer)

rootReducer

rootReducer是这样的

const rootReducer = combineReducers({
  // Define a top-level state field named `todos`, handled by `todosReducer`
  todos: todosReducer,
  filters: filtersReducer,
})

todosReducer和filtersReducer就是两个reducer函数定义,接受state和action,返回新的state,如下所示:

export default function todosReducer(state = initialState, action) {
  switch (action.type) {
    case 'todos/todoAdded': {
      const todo = action.payload
      return {
        ...state,
        entities: {
          ...state.entities,
          [todo.id]: todo,
        },
      }
    }
    ...
    default:
      return state
  }
}

combineReducers接受一个对象,对象的属性值为reducer,返回一个rootReducer,其作用就是将各个小的reducer汇聚成一个大的reducer,其中返回的这个reducer重点需要关注下面这循环逻辑(后续需要仔细说明这段逻辑为何可以将多个recuder汇聚成一个大的reducer)。

function combineReducers(reducers) {
  ...
  return function combination(state, action) {
    ...
    for (var _i = 0; _i < finalReducerKeys.length; _i++) {
      var _key = finalReducerKeys[_i];
      var reducer = finalReducers[_key];
      var previousStateForKey = state[_key];
      var nextStateForKey = reducer(previousStateForKey, action);
      ...
      nextState[_key] = nextStateForKey;
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    }
    hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;
    return hasChanged ? nextState : state;
  };
}

至此我们的rootReducer方法被创建出来,梳理一下,rootReducer方法通过combineReducers将各个小的reducer汇聚成一个根部reducer,rootReducer也是一个reducer方法。

composedEnhancer

回到createStore方法,第二个参数是composedEnhancer,这个例子这里应用了thunk中间件thunkMiddleware

const composedEnhancer = composeWithDevTools(
  applyMiddleware(thunkMiddleware)
  // other store enhancers if any
)

const store = createStore(rootReducer, composedEnhancer)

我可以下从createStore方法中找到使用composedEnhancer的下方代码,我们可以看出,enhancer一个函数,是接受createStore方法,返回一个函数,且该函数依然可以和createStore一样接受reducer和preloadedState。

  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState;
    preloadedState = undefined;
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error(process.env.NODE_ENV === "production" ? formatProdErrorMessage(1) : "Expected the enhancer to be a function. Instead, received: '" + kindOf(enhancer) + "'");
    }

    return enhancer(createStore)(reducer, preloadedState);
  }

这里的enhancer,就是这里的composedEnhancer。

const composedEnhancer = composeWithDevTools(
  applyMiddleware(thunkMiddleware)
  // other store enhancers if any
)

这里就需要研究下composeWithDevTools、applyMiddleware和thunkMiddleware。

composeWithDevTools

主要调用compose.apply(null, arguments),arguments是这里的applyMiddleware(thunkMiddleware),这里通过看composeWithDevTools函数,我们知道composedEnhancer(applyMiddleware(thunkMiddleware)),首先依然返回applyMiddleware(thunkMiddleware)本身,如果存在多个,则依次嵌套调用(...args) => f(g(h(...args))),我们再看applyMiddleware函数。

exports.composeWithDevTools =
  typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    : function () {
        if (arguments.length === 0) return undefined;
        if (typeof arguments[0] === 'object') return compose;
        return compose.apply(null, arguments);
      };

redux的compose,这里我们只使用了一个中间件,就返回这个中间件本身,否则返回一个类似(...args) => f(g(h(...args)))的函数,执行此函数,会依次嵌套调用中间件函数

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for
 * the resulting composite function.
 *
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (...args) => f(g(h(...args))).
 */

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return (arg) => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

applyMiddleware

function applyMiddleware() {
  for (var _len = arguments.length, middlewares = new Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  return function (createStore) {
    return function () {
      ...
  };
}

这里,applyMiddleware(thunkMiddleware),其实是以下这个函数,柯里化函数内部逻辑为依次调用中间件函数,并向中间件函数传入dispatch参数

(createStore) => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args),
    }
    const chain = middlewares.map((middleware) => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch,
    }
  }

thunkMiddleware

thunkMiddleware就是我们下面这里的middleware函数,接受一个参数_ref,拥有dispatch和getState方法,其实就是上面applyMiddleware(thunkMiddleware)的middlewareAPI,它判断action是一个函数,那么就执行这个aciton函数,可以猜测,后面肯定在dispatch action的时候,在本身redux的某段逻辑中嵌入了这段代码,才能使得redux的dispatch除了可以接受对象,也可以接受函数。 上面的chain数组中的item就是(next)=>(action)=>{...},这里的next就是上面的store.dispatch

function createThunkMiddleware<
  State = any,
  BasicAction extends Action = AnyAction,
  ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {
  // Standard Redux middleware definition pattern:
  // See: https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
  const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
    ({ dispatch, getState }) =>
    next =>
    action => {
      // The thunk middleware looks for any functions that were passed to `store.dispatch`.
      // If this "action" is really a function, call it and return the result.
      if (typeof action === 'function') {
        // Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
        return action(dispatch, getState, extraArgument)
      }

      // Otherwise, pass the action down the middleware chain as usual
      return next(action)
    }
  return middleware
}

var thunk = createThunkMiddleware();

createStore

这里我们分析完createStore的两个参数,reducer和enhancer,接着看下 createStore具体代码

enhancer增强dispatch方法

首先第一步,如果存在enhancer,那么这样调用

enhancer(createStore)(reducer, preloadedState)

我们前面看到enhancer其实就是以下函数,执行enhancer(createStore)(reducer, preloadedState),会依然先调用createStore函数,计算出store,然后调用中间件,返回增强后的store,主要是对dispatch进行了增强。

dispatch增加的具体逻辑为:这里的middlewares就是({ dispatch, getState }) => next => action => {return typeof action === 'function' ? action(dispatch, getState, extraArgument ): next(action)} 此时,dispatch函数为action => {return typeof action === 'function' ? action(dispatch, getState, extraArgument ): next(action)},相比createStore内置的dispatch方法,增加了一个判断,aciton如果是一个函数,就返回调用action函数的结果,普通的action对象还是走原来的dispatch逻辑

(createStore) => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args),
    }
    const chain = middlewares.map((middleware) => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch,
    }
  }

内部维护的变量和函数

回到createStore本身逻辑,首先函数内部维护了这几个变量和几个关键函数

  var currentReducer = reducer;
  var currentState = preloadedState;
  var currentListeners = [];
  var nextListeners = currentListeners;
  var isDispatching = false;
  function ensureCanMutateNextListeners(){...}
  function getState(){...}
  function subscribe(listener){...}
  function dispatch(action){...}
  function replaceReducer(nextReducer){...}

初始化store的值

然后dispatch一个init的action,初始化store的值

  dispatch({
    type: ActionTypes.INIT
  });

dispatch实现

dispatch具体实现关键代码逻辑,调用我们定义的reducer,也就是我们combineReducers出来的rootReducer,计算出我们我们的store的初始值,然后赋值给currentState,然后调用循环执行listeners,返回action本身。

  function dispatch(action) {
    ...
    try {
      isDispatching = true;
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

    var listeners = currentListeners = nextListeners;

    for (var i = 0; i < listeners.length; i++) {
      var listener = listeners[i];
      listener();
    }

    return action;
  }

combineReducers中具体具体依次调用每一个reducer,如下:

    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      ...
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state

最后将初始化的值即我们定义的initialState返回,此时返回值(即store)为这个对象:

{
    todos:{
      status: 'idle',
      entities: {},
    },
    filters:{
      status: StatusFilters.All,
      colors: [],
    }
}

返回store对象

最后,返回store对象,如下:

{
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable,
  }

dispatch(fetchTodos())

初始化store之后,调用dispatch(fetchTodos())

import { fetchTodos } from './features/todos/todosSlice'

store.dispatch(fetchTodos())

正常来说,dispatch的参数需要是一个aciton,一个纯对象,但是这里我们使用thunkMiddle对dispatch函数进行了增强,可以接受函数,并且返回函数执行结果

export const fetchTodos = () => async (dispatch) => {
 // 修改todos的status值为loading 
  dispatch(todosLoading())
  const response = await client.get('/fakeApi/todos')
  dispatch(todosLoaded(response.todos))
}
action => {
  // The thunk middleware looks for any functions that were passed to `store.dispatch`.
  // If this "action" is really a function, call it and return the result.
  if (typeof action === 'function') {
    // Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
    return action(dispatch, getState, extraArgument)
  }

  // Otherwise, pass the action down the middleware chain as usual
  return next(action)
}

Provider组件

源码

function Provider({ store, context, children }) {
  const contextValue = useMemo(() => {
    const subscription = createSubscription(store)
    subscription.onStateChange = subscription.notifyNestedSubs
    return {
      store,
      subscription,
    }
  }, [store])

  const previousState = useMemo(() => store.getState(), [store])

  useIsomorphicLayoutEffect(() => {
    const { subscription } = contextValue
    subscription.trySubscribe()

    if (previousState !== store.getState()) {
      subscription.notifyNestedSubs()
    }
    return () => {
      subscription.tryUnsubscribe()
      subscription.onStateChange = null
    }
  }, [contextValue, previousState])

  const Context = context || ReactReduxContext

  return <Context.Provider value={contextValue}>{children}</Context.Provider>
}

使用


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

使用Provider组件时,我们只传入了store对象,这时候,Context对象会直接使用react-redux中的唯一变量ReactReduxContext

export const ReactReduxContext = /*#__PURE__*/ React.createContext(null)

传入Context.Provider组件的value值为contextValue

  const contextValue = useMemo(() => {
    const subscription = createSubscription(store)
    subscription.onStateChange = subscription.notifyNestedSubs
    return {
      store,
      subscription,
    }
  }, [store])

并且在Provider组件中,使用useIsomorphicLayoutEffect(类似useEffect)监听了store state的数据变化,当数据发生变化时,通知嵌套组件。

useIsomorphicLayoutEffect(() => {
    const { subscription } = contextValue
    subscription.trySubscribe()

    if (previousState !== store.getState()) {
      subscription.notifyNestedSubs()
    }
    return () => {
      subscription.tryUnsubscribe()
      subscription.onStateChange = null
    }
  }, [contextValue, previousState])

subscription

有三个阶段,创建阶段,和使用阶段和销毁阶段。

创建

通过createSubscription创建,并且修改onStateChange方法指向notifyNestedSubs方法。

  const contextValue = useMemo(() => {
    const subscription = createSubscription(store)
    subscription.onStateChange = subscription.notifyNestedSubs
    return {
      store,
      subscription,
    }
  }, [store])
createSubscription

一个纯返回subscription对象的方法,但是这里需要关注他的参数,函数是接受两个参数的,但是在Provider组件中createSubscription(store),只传入了store

export function createSubscription(store, parentSub) {
  ...
  const subscription = {
    addNestedSub,
    notifyNestedSubs,
    handleChangeWrapper,
    isSubscribed,
    trySubscribe,
    tryUnsubscribe,
    getListeners: () => listeners,
  }

  return subscription
}

使用

首先是调用了trySubscribe方法 然后就是在store中的数据发送变化的时候,执行notifyNestedSubs

subscription.trySubscribe()

if (previousState !== store.getState()) {
  subscription.notifyNestedSubs()
}
trySubscribe

可以看到因为在这里创建的subscription时是没有parentSub参数的,所以unsubscribe被赋值store.subscribe(handleChangeWrapper)也就是redux中store对象的subscribe方法,并且向store订阅,store中数据发生改变时,要调用handleChangeWrapper,也就是onStateChange方法,也就是notifyNestedSubs方法,执行listeners.notify()

  function trySubscribe() {
    if (!unsubscribe) {
      unsubscribe = parentSub
        ? parentSub.addNestedSub(handleChangeWrapper)
        : store.subscribe(handleChangeWrapper)

      listeners = createListenerCollection()
    }
  }

而listener通过createListenerCollection()创建,createListenerCollection方法源码如下,也是返回一个对象,包含clear,notify,get,subscribe方法的对象。

function createListenerCollection() {
  const batch = getBatch()
  let first = null
  let last = null

  return {
    clear() {
      first = null
      last = null
    },

    notify() {
    ...
    },

    get() {
    ...
    },

    subscribe(callback) {
     ...
    },
  }
}

总而言之,就是store中数据发生改变时,调用listeners的notify方法,也就是执行如下代码,不断执行listener和其下一个节点对象的callback回调。

let listener = first
while (listener) {
  listener.callback()
  listener = listener.next
}
notifyNestedSubs
  function tryUnsubscribe() {
    if (unsubscribe) {
      unsubscribe()
      unsubscribe = undefined
      listeners.clear()
      listeners = nullListeners
    }
  }

销毁

  subscription.tryUnsubscribe()
  subscription.onStateChange = null

useSelector

这里是使用useSelector的一个例子

import { useSelector } from 'react-redux'

const loadingStatus = useSelector((state) => state.todos.status)

使用createSelectorHook创建useSelector

useSelector创建阶段,通过执行高阶函数createSelectorHook获取useSelector函数,在执行createSelectorHook的阶段,劫持了context对象,如果有传入context就使用传进来的context,否则使用react-redux使用的context。

  const useReduxContext =
    context === ReactReduxContext
      ? useDefaultReduxContext
      : () => useContext(context)

useSelector执行阶段

useSelector执行阶段,从context对象中取出store对象和subscription对象,context对象,是我们在使用Provider组件时,在Provider组件传递给Context.Provider的value(contextValue),store对象是我们通过redux的createStore创建的store对象,subscription是我们通过react-redux内部的createSubscription方法创建的对象。 useSelector返回的store中对应state数据对象,通过useSelectorWithStoreAndSubscription方法。

function useSelector(){
    const { store, subscription: contextSub } = useReduxContext()

    const selectedState = useSelectorWithStoreAndSubscription(
      selector,
      equalityFn,
      store,
      contextSub
    )
    
    return selectedState
}

useSelectorWithStoreAndSubscription

参数介绍

useSelectorWithStoreAndSubscription传入了四个参数,selector,equalityFn,store,contextSubselector也就是我们使用时的写的(state)=>state.x.x,equalityFn一个比较相等的方法即const refEquality = (a, b) => a === b,store,通过redux的createStore创建的store对象,contextSubselector也就是在Provider组件中通过createSubscription方法创建的对象。

逻辑

核心逻辑就是要取出目标state数据selectedState,核心代码如下:

const storeState = store.getState()
const newSelectedState = selector(storeState)

此外,关于selectedState的值还有部分缓存逻辑,如果selector和state并没有发生过变化,或者select出来的值相等,那么就取缓存值latestSelectedState.current,详见这段代码:

  try {
    if (
      selector !== latestSelector.current ||
      storeState !== latestStoreState.current ||
      latestSubscriptionCallbackError.current
    ) {
      const newSelectedState = selector(storeState)
      // ensure latest selected state is reused so that a custom equality function can result in identical references
      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
  }

创建新的subscription

在使用useSelector的组件中创建了一个新的subscription,并且将Provider组件中创建出来的subscription作为第二个参数contextSub传递进去,并且trySubscribe

  const subscription = useMemo(
    () => createSubscription(store, contextSub),
    [store, contextSub]
  )
  ...    
  subscription.onStateChange = checkForUpdates
  subscription.trySubscribe()

因为此时parentSub为Provider组件中创建出来的subscription,于是调用parentSub.addNestedSub(handleChangeWrapper),重新trySubscribe,因为Provider组件中unsubscribe已经有值,所以只执行listeners.subscribe(listener)

  function addNestedSub(listener) {
    trySubscribe()
    return listeners.subscribe(listener)
  }
  
  function trySubscribe() {
    if (!unsubscribe) {
      unsubscribe = parentSub
        ? parentSub.addNestedSub(handleChangeWrapper)
        : store.subscribe(handleChangeWrapper)

      listeners = createListenerCollection()
    }
  }

此时,会执行Provider组件创建的subscription持有的createSubscription方法的内部变量listeners的subscribe方法,此时callback就是checkForUpdates方法(自增变量强制刷新UI)。 subscribe中的关键逻辑就是完成了对象的链接调用。 第一个useSelector过来时,last、first都被{callback,next: null,prev: null},命名为A对象 第二个useSelector过来时,listener和last都被赋值{callback,next: null,prev: A},命名为B对象,并且B.prev=A,则通过该引用,修改A对象的next为B,则完成了A到B的链式链接。

    subscribe(callback) {
      let isSubscribed = true

      let listener = (last = {
        callback,
        next: null,
        prev: last,
      })

      if (listener.prev) {
        listener.prev.next = listener
      } else {
        first = listener
      }

      return function unsubscribe() {
        if (!isSubscribed || first === null) return
        isSubscribed = false

        if (listener.next) {
          listener.next.prev = listener.prev
        } else {
          last = listener.prev
        }
        if (listener.prev) {
          listener.prev.next = listener.next
        } else {
          first = listener.next
        }
      }
    },

我们前面看到在Provider组件中,state发生更新时会调用notify方法,就是执行callback方法,也就是我们上面的那个useselector里面的checkForUpdates方法,并且是链式调用,通过该对象的next对象,可以找到下一个注册对象。

    notify() {
      batch(() => {
        let listener = first
        while (listener) {
          listener.callback()
          listener = listener.next
        }
      })
    },

checkForUpdates

forceRender更新UI的关键,只要调用forceRender,就会重新渲染UI,但是这里为了避免其他组件的state中值发生变化,影响到自己这个组件的渲染,这里判断了如果store state不发生变化,或者SelectedState和缓存的一样,则不forceRender,避免了没必要的渲染。

const [, forceRender] = useReducer((s) => s + 1, 0)

function checkForUpdates() {
  try {
    const newStoreState = store.getState()
    // Avoid calling selector multiple times if the store's state has not changed
    if (newStoreState === latestStoreState.current) {
      return
    }

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

Connect组件

使用

import React, { Component } from 'react'
import { connect } from 'react-redux'

const initialValue = {
  value: 1,
}
export const RootReducer = (state = initialValue, action) => {
  if (action.type === 'App') {
    return {
      ...state,
      ...action.payload,
    }
  }
  return state
}

class AppConnect extends Component {
  render() {
    return (
      <div>
        this is root page, and state is {this.props.RootPage.value}
        <button
          style={{
            marginLeft: 10,
          }}
          onClick={() => this.props.add(this.props.RootPage.value + 1)}
        >
          click to add Root value
        </button>
        <br></br>
        {/* <PageA></PageA> */}
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    RootPage: state.RootPage,
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    add: (value) =>
      dispatch({
        payload: { value },
        type: 'App',
      }),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(AppConnect)

大概流程

connect通过createConnect创建,大概返回这样一个函数,这个函数接受我们使用者定义的mapStateToProps、mapDispatchToProps

function connect(mapStateToProps, mapDispatchToProps, mergeProps, _ref2) {
    return connectHOC(selectorFactory....)
}

connectHOC即connectAdvanced,即执行connectAdvanced返回的高阶组件,接受参数为我们使用这定义的UI组件,也就是组件wrapWithConnect,wrapWithConnect组件接收我们的UI组件,。

function wrapWithConnect(WrappedComponent) {
    ...
}

详细分析

createConnect

whenMapStateToPropsIsMissing,判断mapStateToProps不为空,且是函数的话,就返回wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')的执行结果,这个函数有三个作用,1,2,3

Detects whether the mapToProps function being called depends on props, which is used by selectorFactory to decide if it should reinvoke on props changes. On first call, handles mapToProps if returns another function, and treats that new function as the true mapToProps for subsequent calls. On first call, verifies the first result is a plain object, in order to warn the developer that their mapToProps function is not returning a valid result.

initMapStateToProps

也就是说initMapStateToProps其实就是wrapMapToPropsFunc的执行结果,也就是函数initProxySelector,wrapMapToPropsFunc函数第一个参数为mapStateToProps或者mapDispatcgToProps,第二个参数为'mapStateToProps''mapDispatchToProps'

function initProxySelector(dispatch, { displayName }) {
    const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
      return proxy.dependsOnOwnProps
        ? proxy.mapToProps(stateOrDispatch, ownProps)
        : proxy.mapToProps(stateOrDispatch)
    }

    // allow detectFactoryAndVerify to get ownProps
    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)
      }

      if (process.env.NODE_ENV !== 'production')
        verifyPlainObject(props, displayName, methodName)

      return props
    }

    return proxy
  }

initMergeProps

initMergeProps就是以下函数initMergePropsProxy

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

selectorFactory

selectorFactory,即finalPropsSelectorFactory

export 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') {
    verifySubselectors(
      mapStateToProps,
      mapDispatchToProps,
      mergeProps,
      options.displayName
    )
  }

  const selectorFactory = options.pure
    ? pureFinalPropsSelectorFactory
    : impureFinalPropsSelectorFactory

  return selectorFactory(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    dispatch,
    options
  )
}

connectAdvanced

connectAdvanced函数入参,connectOptions包含如下属性:

  initMapStateToProps,
  initMapDispatchToProps,
  initMergeProps,
  pure,
  areStatesEqual,
  areOwnPropsEqual,
  areStatePropsEqual,
  areMergedPropsEqual,

并接受了context,默认context为ReactReduxContext,也就是React.createContext(null),也就是在Provider组件有使用到的context

wrapWithConnect

高阶组件wrapWithConnect,接受我们定义的UI组件,即代码中的WrappedComponent,返回一个新的组件,这里主要有如下几个处理:

  • 首先,如果默认的pure模式下,会给返回的ConnectFunction的组件包一层React.memo,防止其他组件依赖store中的部分其他state数据发生变化。
  • 另外就是,通过hoistStatics(Connect, WrappedComponent)将WrappedComponent组件的静态属性复制给Connect,并返回新组件。
  • 如果明确forwardRef,回将forwarded接受的ref函数赋值给reactReduxForwardedRef,而在ConnectFunction组件内部,reactReduxForwardedRef又赋值给了WrappedComponent的ref,以方便我们直接获取到UI组件的ref引用。
      ...
      const renderedWrappedComponent = useMemo(
        () => (
          <WrappedComponent
            {...actualChildProps}
            ref={reactReduxForwardedRef}
          />
        ),
        [reactReduxForwardedRef, WrappedComponent, actualChildProps]
      )
    ...

    const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction

    Connect.WrappedComponent = WrappedComponent
    Connect.displayName = ConnectFunction.displayName = displayName

    if (forwardRef) {
      const forwarded = React.forwardRef(function forwardConnectRef(
        props,
        ref
      ) {
        return <Connect {...props} reactReduxForwardedRef={ref} />
      })

      forwarded.displayName = displayName
      forwarded.WrappedComponent = WrappedComponent
      return hoistStatics(forwarded, WrappedComponent)
    }

    return hoistStatics(Connect, WrappedComponent)

ConnectFunction

有点长,这里分段解读该段函数 首先,这里确定了要使用的context,并取出从context.Provider中传入的对象,也就是包含store和subscribtion属性的对象

      const [propsContext, reactReduxForwardedRef, wrapperProps] =
        useMemo(() => {
          // Distinguish between actual "data" props that were passed to the wrapper component,
          // and values needed to control behavior (forwarded refs, alternate context instances).
          // To maintain the wrapperProps object reference, memoize this destructuring.
          const { reactReduxForwardedRef, ...wrapperProps } = props
          return [props.context, reactReduxForwardedRef, wrapperProps]
        }, [props])

      const ContextToUse = useMemo(() => {
        // Users may optionally pass in a custom context instance to use instead of our ReactReduxContext.
        // Memoize the check that determines which context instance we should use.
        return propsContext &&
          propsContext.Consumer &&
          isContextConsumer(<propsContext.Consumer />)
          ? propsContext
          : Context
      }, [propsContext, Context])

      // Retrieve the store and ancestor subscription via context, if available
      const contextValue = useContext(ContextToUse)

这里,判断store是从props传入(我们手动传入),还是从最近的context.Provider传入,取出redux的store对象

const store = didStoreComeFromProps ? props.store : contextValue.store
const childPropsSelector = useMemo(() => {
// The child props selector needs the store reference as an input.
// Re-create this selector whenever the store changes.
return createChildSelector(store)
}, [store])

childPropsSelector,即这里的selectorFactory(...),即函数pureFinalPropsSelector,handleFirstCall和handleSubsequentCalls主要处理props的合并结果并返回,这里我们就明白了childPropsSelector主要是计算props,将我们传入的mapState和mapDispatch的属性都合并到props中,并且在后面我们可以看到通过childPropsSelector计算出actualChildProps,并放入了WrappedComponent组件属性中,这样我们的UI组件的props就能拿到mapStateToProps和mapDispatchToProps的属性和方法

  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
  }
  ...
  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
  }
  ...
  function pureFinalPropsSelector(nextState, nextOwnProps) {
    return hasRunAtLeastOnce
      ? handleSubsequentCalls(nextState, nextOwnProps)
      : handleFirstCall(nextState, nextOwnProps)
  }
export 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') {
    verifySubselectors(
      mapStateToProps,
      mapDispatchToProps,
      mergeProps,
      options.displayName
    )
  }

  const selectorFactory = options.pure
    ? pureFinalPropsSelectorFactory
    : impureFinalPropsSelectorFactory

  return selectorFactory(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    dispatch,
    options
  )
}
...
...
function createChildSelector(store) {
  return selectorFactory(store.dispatch, selectorFactoryOptions)
}
...
const childPropsSelector = useMemo(() => {
    // The child props selector needs the store reference as an input.
    // Re-create this selector whenever the store changes.
    return createChildSelector(store)
}, [store])

通过childPropsSelector(store.getState(), wrapperProps)计算出actualChildProps

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

监听actualChildProps触发notifyNestedSubs

function captureWrapperProps(
  lastWrapperProps,
  lastChildProps,
  renderIsScheduled,
  wrapperProps,
  actualChildProps,
  childPropsFromStoreUpdate,
  notifyNestedSubs
) {
  // We want to capture the wrapper props and child props we used for later comparisons
  lastWrapperProps.current = wrapperProps
  lastChildProps.current = actualChildProps
  renderIsScheduled.current = false

  // If the render was from a store update, clear out that reference and cascade the subscriber update
  if (childPropsFromStoreUpdate.current) {
    childPropsFromStoreUpdate.current = null
    notifyNestedSubs()
  }
}
...
      useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [
        lastWrapperProps,
        lastChildProps,
        renderIsScheduled,
        wrapperProps,
        actualChildProps,
        childPropsFromStoreUpdate,
        notifyNestedSubs,
      ])

监听props变化,触发shouldHandleStateChanges作用为,这里跟useSelector里面的逻辑一致,主要配合useReducer生成的forceComponentUpdateDispatch实现UI的更新。

function subscribeUpdates(
  shouldHandleStateChanges,
  store,
  subscription,
  childPropsSelector,
  lastWrapperProps,
  lastChildProps,
  renderIsScheduled,
  childPropsFromStoreUpdate,
  notifyNestedSubs,
  forceComponentUpdateDispatch
) {
  // If we're not subscribed to the store, nothing to do here
  if (!shouldHandleStateChanges) return

  // Capture values for checking if and when this component unmounts
  let didUnsubscribe = false
  let lastThrownError = null

  // We'll run this callback every time a store subscription update propagates to this component
  const checkForUpdates = () => {
    if (didUnsubscribe) {
      // Don't run stale listeners.
      // Redux doesn't guarantee unsubscriptions happen until next dispatch.
      return
    }

    const latestStoreState = store.getState()

    let newChildProps, error
    try {
      // Actually run the selector with the most recent store state and wrapper props
      // to determine what the child props should be
      newChildProps = childPropsSelector(
        latestStoreState,
        lastWrapperProps.current
      )
    } catch (e) {
      error = e
      lastThrownError = e
    }

    if (!error) {
      lastThrownError = null
    }

    // If the child props haven't changed, nothing to do here - cascade the subscription update
    if (newChildProps === lastChildProps.current) {
      if (!renderIsScheduled.current) {
        notifyNestedSubs()
      }
    } else {
      // Save references to the new child props.  Note that we track the "child props from store update"
      // as a ref instead of a useState/useReducer because we need a way to determine if that value has
      // been processed.  If this went into useState/useReducer, we couldn't clear out the value without
      // forcing another re-render, which we don't want.
      lastChildProps.current = 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
      forceComponentUpdateDispatch({
        type: 'STORE_UPDATED',
        payload: {
          error,
        },
      })
    }
  }

  // Actually subscribe to the nearest connected ancestor (or store)
  subscription.onStateChange = checkForUpdates
  subscription.trySubscribe()

  // Pull data from the store after first render in case the store has
  // changed since we began.
  checkForUpdates()

  const unsubscribeWrapper = () => {
    didUnsubscribe = true
    subscription.tryUnsubscribe()
    subscription.onStateChange = null

    if (lastThrownError) {
      // It's possible that we caught an error due to a bad mapState function, but the
      // parent re-rendered without this component and we're about to unmount.
      // This shouldn't happen as long as we do top-down subscriptions correctly, but
      // if we ever do those wrong, this throw will surface the error in our tests.
      // In that case, throw the error from here so it doesn't get lost.
      throw lastThrownError
    }
  }

  return unsubscribeWrapper
}
...
      useIsomorphicLayoutEffectWithArgs(
        subscribeUpdates,
        [
          shouldHandleStateChanges,
          store,
          subscription,
          childPropsSelector,
          lastWrapperProps,
          lastChildProps,
          renderIsScheduled,
          childPropsFromStoreUpdate,
          notifyNestedSubs,
          forceComponentUpdateDispatch,
        ],
        [store, subscription, childPropsSelector]
      )

返回组件renderedChild,如果shouldHandleStateChanges为true,就在我们定义的UI组件外层包一个ContextToUse.Provide,然后里面放renderedWrappedComponent,renderedWrappedComponent组件就是我们的UI组件,将计算完的props和ref放进去组件

      const renderedWrappedComponent = useMemo(
        () => (
          <WrappedComponent
            {...actualChildProps}
            ref={reactReduxForwardedRef}
          />
        ),
        [reactReduxForwardedRef, WrappedComponent, actualChildProps]
      )

      // If React sees the exact same element reference as last time, it bails out of re-rendering
      // that child, same as if it was wrapped in React.memo() or returned false from shouldComponentUpdate.
      const renderedChild = useMemo(() => {
        if (shouldHandleStateChanges) {
          // If this component is subscribed to store updates, we need to pass its own
          // subscription instance down to our descendants. That means rendering the same
          // Context instance, and putting a different value into the context.
          return (
            <ContextToUse.Provider value={overriddenContextValue}>
              {renderedWrappedComponent}
            </ContextToUse.Provider>
          )
        }

        return renderedWrappedComponent
      }, [ContextToUse, renderedWrappedComponent, overriddenContextValue])

      return renderedChild

思考

为什么要由Provider提供的subscription去链式的触发订阅,而不是在useSelector内部直接向redux的store订阅(可能和UI的更新机制有关)

技术总结

函数柯里化

函数柯里化,比如这里的compose函数,applyMiddleWares,MiddleWare本身都有应用,作用如下:

  • 参数复用
  • 提前返回
  • 延迟执行 可以降低试用返回,提高适用性

闭包

store中的state,useSelector中常见的listener,以及函数柯里化都是闭包的经典应用

双向链表实现

listener之{callback,next: null,prev: A}