Redux源码分析

212 阅读8分钟

React

Redux三大基本原则

  • 单一数据源 受益于单一的 state tree ,以前难以实现的如“撤销/重做”这类功能也变得轻而易举

  • state是只读的 唯一改变 state 的方法就是触发 action

  • 使用纯函数来执行修改 为了描述 action 如何改变 state tree ,你需要编写 reducers,并且reducers中不能包含副作用

Redux中的一些概念

  • state: 全局的状态对象,唯一且不可变。
  • store: 调用createStore 函数生成的对象,里面封入了定义在createStore内部用于操作全局状态的方法,用户通过这些方法使用Redux。
  • action: 描述状态如何修改的对象,固定拥有一个type属性,通过store的dispatch方法提交。
  • reducer: 实际执行状态修改的纯函数,由用户定义并传入,接收来自dispatch的

action作为参数,计算返回全新的状态,完成state的更新,然后执行订阅的监听函数。

  • storeEnhancer: createStore的高阶函数封装,用于加强store的能力,redux提供的applyMiddleware是官方实现的一个storeEnhancer。
  • middleware: dispatch的高阶函数封装,由applyMiddleware把原dispatch替换为包含middleware链式调用的实现

用法示例

首先来看一下源码的example里面的一个最基本的用例


import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import Counter from './components/Counter'
import counter from './reducers'

const store = createStore(counter)
const rootEl = document.getElementById('root')

const render = () => ReactDOM.render(
  <Counter
    value={store.getState()}
    onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
    onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
  />,
  rootEl
)

render()

//订阅渲染函数,当状态变更时,执行render
store.subscribe(render)

Redux源码解读

createStore

createStore 是一个大的闭包环境,里面定义了store本身,以及store的各种api。所有的state,reducer,action等都在这里面维护

如果有storeEnhancer,则应用storeEnhancer,storeEnhancer从字面上理解,就是一个store的增强器,最后也是返回store

image.png

如果没有storeEnhancer,则初始化state(通过dispatch一个随机值),并返回store提供的各种方法

image.png

getState

其实就是返回当前的state

image.png

dispatch

dispatch做了两件事:

  1. 基于reducer计算state
  2. 调用订阅的函数 image.png

subscribe

订阅监听函数,并返回取消订阅函数

image.png

applyMiddleware

applyMiddleware是官方实现的一个storeEnhance,用于在dispatch过程中添加中间件,核心原理就是对原有的dispatch进行包装

用法:

const logger1 = ({dispatch, getState}) =>  next => action => {
  console.log('middleware1 start')
  next(action);
  console.log('middleware1 end')
}

const logger2 = ({dispatch, getState}) =>  next => action => {
  console.log('middleware2 start')
   action = next(action);
  console.log('middleware2 end')
}

const store = createStore(rootReducer, preloadState, applyMiddleWare(logger1, logger2));


源码分析

image.png

我们可以看到,applyMiddleware的本质就是通过compose,包装中间件,以实现链式调用。 那么,compose是什么呢?compose是函数式编程中的一种思想,,它创建一个从右到左的数据流,右边函数执行的结果作为参数传入左边。

image.png

redux-thunk

redux-thunk 使 redux 支持asyncAction ,它经常被用于一些异步的场景中。 redux-thunk本质就是dispatch一个thunk函数,只需判断action是否为函数即可

用法


// 下面就是一个 thunk。

function makeASandwichWithSecretSauce(forPerson) {

// 控制反转!

// 返回一个接收 `dispatch` 的函数。

// Thunk middleware 知道如何把异步的 thunk action 转为普通 action。

return function(dispatch) {

return fetchSecretSauce().then(

sauce => dispatch(makeASandwich(forPerson, sauce)),

error => dispatch(apologize('The Sandwich Shop', forPerson, error))

)

}

}

// Thunk middleware 可以让我们像 dispatch 普通 action一样 dispatch 异步的 thunk action。

store.dispatch(makeASandwichWithSecretSauce('Me'))

原理

image.png

React-redux

Redux实现了全局的状态管理,并且它与具体的UI框架不耦合,因此也可以用在其他UI框架中。React-redux是官方实现的React UI bindings,它仅用于React,并且其中做了许多工作来避免不必要的重新渲染。 它提供了两种使用Redux的方式:HOC和Hooks,分别对应Class组件和函数式组件。下面分别看看这两种方式的实现原理

HOC方式

image.png

用法


import * as actionCreators from './actionCreators'
import { bindActionCreators } from 'redux'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'

function mapStateToProps(state) {
  return { todos: state.todos }
}

function mapDispatchToProps(dispatch) {
  return { actions: bindActionCreators(actionCreators, dispatch) }
}

TodoApp = connect(mapStateToProps, mapDispatchToProps)(TodoApp)

const store = createStore(reducer)

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

原理

Provider原理

Provider主要做了两件事情

  • 订阅reduxsubscribe()事件
  • Subscription实例传入Context方便子级订阅

image.png

可以看到上面出现的createSubscription,这个函数创建一个订阅器,有发布订阅功能

image.png

connect connect建立组件和状态之间的连接,状态变化,组件更新,其中有关于避免重新渲染的处理

//connect.js

//遍历执行函数,将arg作为参数传入,如果有结果,则return
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
      }.`
    )
  }
}

function strictEqual(a, b) {
  return a === b
}

export function createConnect({
  connectHOC = 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
    } = {}
  ) {
      
      //依次执行mapStateToPropsFactories里的函数
    const initMapStateToProps = match(
      mapStateToProps,
      mapStateToPropsFactories,
      'mapStateToProps'
    )
    const initMapDispatchToProps = match(
      mapDispatchToProps,
      mapDispatchToPropsFactories,
      'mapDispatchToProps'
    )
    const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')

    return connectHOC(selectorFactory, {
      methodName: 'connect',

      getDisplayName: (name) => `Connect(${name})`,

      shouldHandleStateChanges: Boolean(mapStateToProps),

      // 这些参数最终传入到selectorFactory中
      initMapStateToProps,
      initMapDispatchToProps,
      initMergeProps,
      pure,
      areStatesEqual,
      areOwnPropsEqual,
      areStatePropsEqual,
      areMergedPropsEqual,
    })
  }
}

可以看到我们传入的mapStateToPropsmapDispatchToPropsmergeProps实际上是通过了一个match()函数的包装校验

这里就以mapStateToPropsFactories也就是defaultMapStateToPropsFactories为例

顺着mapStateToPropsFactories看,可以发现执行到了wrapMapToPropsFunc函数

//wrapMapToProp.js

//如果dependsOnOwnProps存在的话,就根据dependsOnOwnProps来确定,如果不存在,就根据mapToProps的参数个数来确定
export function getDependsOnOwnProps(mapToProps) {
  return mapToProps.dependsOnOwnProps !== null &&
    mapToProps.dependsOnOwnProps !== undefined
    ? Boolean(mapToProps.dependsOnOwnProps)
    : mapToProps.length !== 1
}

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

    //dependsOnOwnProps属性决定当组件的props改变时是否需要重新调用
    proxy.dependsOnOwnProps = true

    //这里用了代理,detectFactoryAndVerify执行一次后,就会被mapToProps代替,目的是只需确定一次dependsOnOwnProps的值就行
    proxy.mapToProps = function detectFactoryAndVerify(
      stateOrDispatch,
      ownProps
    ) {
      // 第一次调用后就会被我们传入的mapToProps覆盖掉
      proxy.mapToProps = mapToProps
      proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
      let props = proxy(stateOrDispatch, ownProps)

      //如果props是一个函数的情况
      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
  }
}

确定dependsOnOwnProps的值是为了后面selectorFactory做铺垫,,selectorFactory的作用是根据stateprops的变化,判断是否需要调用我们的mapStateToPropsmapDispatchToProps返回新的数据,这里就进行了一次缓存来提升性能,避免重复计算

selectorFactory

selectorFactory中就用到了dependsOnOwnProps,根据state和props变化尽可能重复计算和避免重复更新

//selectorFactory.js

//pure为false,则每次都会调用mapStateToProps方法获得新的数据
export function impureFinalPropsSelectorFactory(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch
) {
  return function impureFinalPropsSelector(state, ownProps) {
    return mergeProps(
      mapStateToProps(state, ownProps),
      mapDispatchToProps(dispatch, ownProps),
      ownProps
    )
  }
}


//pure为true,会判断值是否相同,不相同才调用,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

  //第一次调用Selector初始化把值都存下来,方便后面的比较
  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 handleNewPropsAndNewState() {
    stateProps = mapStateToProps(state, ownProps)

    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps)

    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }

  function handleNewProps() {
    if (mapStateToProps.dependsOnOwnProps)
      stateProps = mapStateToProps(state, ownProps)

    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps)

    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }

  function handleNewState() { 
    const nextStateProps = mapStateToProps(state, ownProps)
    const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
    stateProps = nextStateProps

    if (statePropsChanged)
      mergedProps = mergeProps(stateProps, dispatchProps, ownProps)

    return mergedProps
  }

  function handleSubsequentCalls(nextState, nextOwnProps) {
    const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
    const stateChanged = !areStatesEqual(nextState, state)

    //这里的state指的是store中的对象
    state = nextState
    ownProps = nextOwnProps

    //根据state和props的变化的不同情况,执行不同的处理
    if (propsChanged && stateChanged) return handleNewPropsAndNewState()
    if (propsChanged) return handleNewProps()
    if (stateChanged) return handleNewState()
    return mergedProps
  }

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

  //pure浅比较调用pureFinalPropsSelectorFactory,里面会对比是否需要更新,默认为true
  const selectorFactory = options.pure
    ? pureFinalPropsSelectorFactory
    : impureFinalPropsSelectorFactory

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

回到connect中,可以看到最终调用的是connectAdvanced这个函数,进入这个函数里面看看

import hoistStatics from 'hoist-non-react-statics'
import React, { useContext, useMemo, useRef, useReducer } from 'react'
import { isValidElementType, isContextConsumer } from 'react-is'
import { createSubscription } from '../utils/Subscription'
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'

import { ReactReduxContext } from './Context'

const EMPTY_ARRAY = []
const NO_SUBSCRIPTION_ARRAY = [null, null]

const stringifyComponent = (Comp) => {
  try {
    return JSON.stringify(Comp)
  } catch (err) {
    return String(Comp)
  }
}

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

function useIsomorphicLayoutEffectWithArgs(
  effectFunc,
  effectArgs,
  dependencies
) {
  useIsomorphicLayoutEffect(() => effectFunc(...effectArgs), dependencies)
}

function captureWrapperProps(
  lastWrapperProps,
  lastChildProps,
  renderIsScheduled,
  wrapperProps,
  actualChildProps,
  childPropsFromStoreUpdate,
  notifyNestedSubs
) {
  //获取wrapper props和child props,以便于之后的比较
  lastWrapperProps.current = wrapperProps
  lastChildProps.current = actualChildProps
  renderIsScheduled.current = false

  // 如果更新来自store,则清空引用并且通知子级更新
  if (childPropsFromStoreUpdate.current) {
    childPropsFromStoreUpdate.current = null
    notifyNestedSubs()
  }
}

function subscribeUpdates(
  shouldHandleStateChanges,
  store,
  subscription,
  childPropsSelector,
  lastWrapperProps,
  lastChildProps,
  renderIsScheduled,
  childPropsFromStoreUpdate,
  notifyNestedSubs,
  forceComponentUpdateDispatch
) {
  if (!shouldHandleStateChanges) return

  let didUnsubscribe = false
  let lastThrownError = null

  //每当store的订阅更新传递到此组件都会运行这个回调
  const checkForUpdates = () => {
    if (didUnsubscribe) {
      return
    }

    const latestStoreState = store.getState()

    let newChildProps, error
    try {
      //计算子props
      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 {
      //如果子props发生了变化,则执行渲染
      lastChildProps.current = newChildProps
      childPropsFromStoreUpdate.current = newChildProps
      renderIsScheduled.current = true
      forceComponentUpdateDispatch({
        type: 'STORE_UPDATED',
        payload: {
          error,
        },
      })
    }
  }

  //实际订阅的是最近的父级或者是store
  subscription.onStateChange = checkForUpdates

  //订阅
  subscription.trySubscribe()
  checkForUpdates()

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

    if (lastThrownError) {
  
      throw lastThrownError
    }
  }

  return unsubscribeWrapper
}

const initStateUpdates = () => [null, 0]

export default function connectAdvanced(
  {
    getDisplayName = (name) => `ConnectAdvanced(${name})`,
    methodName = 'connectAdvanced',
    renderCountProp = undefined,
    //false的时候dispatch里组件也不会更新
    shouldHandleStateChanges = true,
    storeKey = 'store',
    withRef = false,
    context = ReactReduxContext,
    ...connectOptions
  } = {}
) {
  if (process.env.NODE_ENV !== 'production') {
    if (renderCountProp !== undefined) {
      throw new Error(
        `renderCountProp is removed. render counting is built into the latest React Dev Tools profiling extension`
      )
    }
    if (withRef) {
      throw new Error(
        'withRef is removed. To access the wrapped instance, use a ref on the connected component'
      )
    }

    const customStoreWarningMessage =
      'To use a custom Redux store for specific components, create a custom React context with ' +
      "React.createContext(), and pass the context object to React Redux's Provider and specific components" +
      ' like: <Provider context={MyContext}><ConnectedComponent context={MyContext} /></Provider>. ' +
      'You may also pass a {context : MyContext} option to connect'

    if (storeKey !== 'store') {
      throw new Error(
        'storeKey has been removed and does not do anything. ' +
          customStoreWarningMessage
      )
    }
  }

  const Context = context

  return function wrapWithConnect(WrappedComponent) {
    if (
      process.env.NODE_ENV !== 'production' &&
      !isValidElementType(WrappedComponent)
    ) {
      throw new Error(
        `You must pass a component to the function returned by ` +
          `${methodName}. Instead received ${stringifyComponent(
            WrappedComponent
          )}`
      )
    }

    const wrappedComponentName =
      WrappedComponent.displayName || WrappedComponent.name || 'Component'

    const displayName = getDisplayName(wrappedComponentName)

    const selectorFactoryOptions = {
      ...connectOptions,
      getDisplayName,
      methodName,
      renderCountProp,
      shouldHandleStateChanges,
      storeKey,
      displayName,
      wrappedComponentName,
      WrappedComponent,
    }

    //是否要缓存值,默认为true
    const { pure } = connectOptions

    function createChildSelector(store) {
      //确定根据props和store中的state变更是否要重新执行mapStateToProps和mapDispatchToProps
      return selectorFactory(store.dispatch, selectorFactoryOptions)
    }

    //如果是非"pure"模式,则不需要缓存值,为了避免条件调用hooks,包装一层
    const usePureOnlyMemo = pure ? useMemo : (callback) => callback()


    //这是经过包装后,渲染在页面上的组件
    function ConnectFunction(props) {
      const [propsContext, reactReduxForwardedRef, wrapperProps] =
        useMemo(() => {
          const { reactReduxForwardedRef, ...wrapperProps } = props
          return [props.context, reactReduxForwardedRef, wrapperProps]
        }, [props])

      const ContextToUse = useMemo(() => {
        // Memoize the check that determines which context instance we should use.
         // 缓存应该使用自带的context还是用户传入的context
        return propsContext &&
          propsContext.Consumer &&
          isContextConsumer(<propsContext.Consumer />)
          ? propsContext
          : Context
      }, [propsContext, Context])
 
      const contextValue = useContext(ContextToUse)
      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.`
        )
      }

      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.
        // createChildSelector需要store作为参数,在store改变的时候会重新创建
        return createChildSelector(store)
      }, [store])

      const [subscription, notifyNestedSubs] = useMemo(() => {
        //这个时候组件不会更新
        if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY

    
        
        //如果组件的store是从props里来的,就不需要传入context里的subscription
        //通过这个订阅store来让组件更新
        const subscription = createSubscription(
          store,
          didStoreComeFromProps ? null : contextValue.subscription
        )

  
        //通知子组件更新的方法
        const notifyNestedSubs =
          subscription.notifyNestedSubs.bind(subscription)

        return [subscription, notifyNestedSubs]
      }, [store, didStoreComeFromProps, contextValue])

    
      const overriddenContextValue = useMemo(() => {
        if (didStoreComeFromProps) {
         
          return contextValue
        }


        //否则将当前组件的subscription放入context里,确保子组件在当前组件更新完之前不会更新
        return {
          ...contextValue,
          subscription,
        }
      }, [didStoreComeFromProps, contextValue, subscription])

     
      //我们需要在redux store更新的时候强制让包装组件更新

      //正常情况下组件重新的渲染就是因为调用了forceComponentUpdateDispatch,而调用这个就是在订阅的事件中
      const [[previousStateUpdateResult], forceComponentUpdateDispatch] =
        useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)

      // Propagate any mapState/mapDispatch errors upwards
      if (previousStateUpdateResult && previousStateUpdateResult.error) {
        throw previousStateUpdateResult.error
      }


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

      //计算出真正的props
      const actualChildProps = usePureOnlyMemo(() => {
    
        //这次渲染也许是因为redux store更新产生了新props触发的
        // 然而,我们也可能在这之后得到父级传入的propsn
        //如果我们得到一个新的child props,和一个相同的父级传入的props,我们知道我们应该使用新的child props(不用重新计算)
        //但是,如果我们有新的包装的props,这些可能会更改子props,我们就必须重新计算
        if (
          childPropsFromStoreUpdate.current &&
          wrapperProps === lastWrapperProps.current
        ) {
          return childPropsFromStoreUpdate.current
        }

        return childPropsSelector(store.getState(), wrapperProps)

        //因为previousStateUpdateResult的改变,意味者这个组件发生了重新渲染,才会重新计算actualChildProps
      }, [store, previousStateUpdateResult, wrapperProps])
  
      // useIsomorphicLayoutEffectWithArgs会根据是服务端还是浏览器端来决定到底调用useEffect还是useLayoutEffect
      /**
       * // 这里主要是初始化值,用做以后更新时的对比
      // 还有就是调用自身的notifyNestedSubs,让子组件也更新
       */
      useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [ 
        lastWrapperProps,
        lastChildProps,
        renderIsScheduled,
        wrapperProps,
        actualChildProps,
        childPropsFromStoreUpdate,
        notifyNestedSubs,
      ])

      /**
       *  只会在store或者subscription改变时候重新订阅
          这里主要绑定订阅事件
       */
      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]
      )

    
      const renderedChild = useMemo(() => {
        if (shouldHandleStateChanges) {
          return (
            <ContextToUse.Provider value={overriddenContextValue}>
              {renderedWrappedComponent}
            </ContextToUse.Provider>
          )
        }

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

      return renderedChild
    }

    //如果是pure模式,确保包裹组件只会在props改变时重渲染
    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)
  }
}

由上面可以看到,connect实际上会创建Provider,实现分层订阅,父组件如果需要渲染,才需要通知子组件再渲染

Hooks方式

用法


import { render } from 'react-dom' 
import { createStore } from 'redux' 
import { Provider,useSelector,useDispatch } from 'react-redux' 

const store = createStore(reducer)

const Child = () => { 
    const searchWords = useSelector(state => state.searchWords)  
    const dispatch = useDispatch() 
    
    const handleChange = (e) => { 
        dispatch({ type: 'UPDATE_SEARCH', payload: e.target.value }) 
    } 
    
    return ( 
    <div> 
        <input type="text" value={searchWords} onChange={handleChange} /> 
    </div> 
     ) 
}

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

原理

我们知道,Provider提供了全局的store和subscription(用于订阅),那么接下来就是需要订阅store中state的变化,useSelector,这个hook向redux subscribe了一个listener,当状态变化时被触发。 useSelector源码

//默认的比较函数
const refEquality = (a, b) => a === b

function useSelectorWithStoreAndSubscription(
  selector,
  equalityFn,
  store,
  contextSub
) {
  const [, forceRender] = useReducer((s) => s + 1, 0)

  const subscription = useMemo(
    () => createSubscription(store, contextSub),
    [store, contextSub]
  )

  //缓存之前的结果,便于之后的比较 
  const latestSubscriptionCallbackError = useRef()
  const latestSelector = useRef()
  const latestStoreState = useRef()
  const latestSelectedState = useRef()

  const storeState = store.getState()
  let selectedState
 
  try {
    if (
      selector !== latestSelector.current ||
      storeState !== latestStoreState.current ||
      latestSubscriptionCallbackError.current
    ) {
      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
  }

  useIsomorphicLayoutEffect(() => {
    latestSelector.current = selector
    latestStoreState.current = storeState
    latestSelectedState.current = selectedState
    latestSubscriptionCallbackError.current = undefined
  })

  useIsomorphicLayoutEffect(() => {
    function checkForUpdates() {
      //检查是否需要更新
      try {
        const newStoreState = store.getState()
        if (newStoreState === latestStoreState.current) {
          //如果storeState前后相等,则返回,不更新
          return
        }

        const newSelectedState = latestSelector.current(newStoreState)

        if (equalityFn(newSelectedState, latestSelectedState.current)) {
          //如果计算出的selectedState前后相等,则返回,不更新
          return
        }

        latestSelectedState.current = newSelectedState
        latestStoreState.current = newStoreState
      } catch (err) {
        latestSubscriptionCallbackError.current = err
      }

      //真正用于更新
      forceRender()
    }


    subscription.onStateChange = checkForUpdates
    //订阅全局的subscription,全局的subscription订阅的store的变化
    subscription.trySubscribe()

    checkForUpdates()

    return () => subscription.tryUnsubscribe()
  }, [store, subscription])

  return selectedState
}

可以看到,相对于HOC的方式,Hooks的方式代码更加精炼,也更加清晰

总结

Redux基于不可变数据的理念,并通过compose的方式实现中间件的链式调用。中间件的机制使得redux的扩展性非常强,这是非常好一种设计理念。 Redux只实现了状态管理,把状态更新的监听能力暴露出去。其他的比如状态缓存,状态对比,更新视图都交给特定的UI bindings,比如React redux,职责明确,值得借鉴。

参考

  1. React-Redux原理简析与源码解析
  2. Redux通关简洁攻略 -- 看这一篇就够了!