Bobolo
  • Home
  • Me

Recoil

浅析 React Redux

15 Min Read

Tue Sep 15 2020

Written By Bobolo

Redux 的介绍已经在之前的文章说明了,这里主要讲的是 React Redux,随着 React Hooks 的推进,Hooks 的代码更贴近 React 的生态,无论在用法还是代码体积都更加友善。

Published on npm

Commons

无论是使用 Hooks 还是 Connect,都需要用到的公共部分

Subscription

import { getBatch } from './batch'

// NOTE:初始化listeners
const CLEARED = null
const nullListeners = { notify() {} }

function createListenerCollection() {
  // NOTE:拿到reactdom的state合并方法,防止订阅过多的setState,造成性能问题
  const batch = getBatch()

  let current = []
  let next = []

  return {
    clear() {
      next = CLEARED
      current = CLEARED
    },
    // NOTE:通知执行订阅的方法
    notify() {
      const listeners = (current = next)
      batch(() => {
        for (let i = 0; i < listeners.length; i++) {
          listeners[i]()
        }
      })
    },

    get() {
      return next
    },
    // NOTE:注册监听
    subscribe(listener) {
      let isSubscribed = true
      // NOTE:确保next获取的是正确的current而不是引用
      if (next === current) next = current.slice()
      next.push(listener)

      return function unsubscribe() {
        if (!isSubscribed || current === CLEARED) return
        isSubscribed = false

        if (next === current) next = current.slice()
        next.splice(next.indexOf(listener), 1)
      }
    },
  }
}

// NOTE:new Subscription(store),parentSub为undefined
export default class Subscription {
  constructor(store, parentSub) {
    this.store = store
    this.parentSub = parentSub
    this.unsubscribe = null
    this.listeners = nullListeners

    this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
  }

  // NOTE:增加嵌套订阅
  addNestedSub(listener) {
    this.trySubscribe()
    return this.listeners.subscribe(listener)
  }

  notifyNestedSubs() {
    this.listeners.notify()
  }

  // NOTE:store收到dispatch会执行nextListeners订阅组,这里provider会将该函数注册到nextListeners中
  handleChangeWrapper() {
    if (this.onStateChange) {
      this.onStateChange()
    }
  }

  isSubscribed() {
    return Boolean(this.unsubscribe)
  }

  // NOTE:没有订阅,就判断parentSub,如果有则帮到parentSub的订阅组中,否则绑到当前store
  trySubscribe() {
    if (!this.unsubscribe) {
      this.unsubscribe = this.parentSub
        ? this.parentSub.addNestedSub(this.handleChangeWrapper)
        : this.store.subscribe(this.handleChangeWrapper) //NOTE:给store的nextListeners增加一个订阅的方法
      this.listeners = createListenerCollection()
    }
  }

  tryUnsubscribe() {
    if (this.unsubscribe) {
      this.unsubscribe()
      this.unsubscribe = null
      this.listeners.clear()
      this.listeners = nullListeners
    }
  }
}

Subscription(工具类),不存在流程等问题,主要作用就是用来生成一个可以订阅的类,重点可以了解实现的逻辑

  • 可以用增加和取消订阅
  • 可以给当前 Store 或者一个订阅的类增加订阅的事件;
  • 可以初始化和通知全部已订阅的事件;

Provider

import React, { useMemo, useEffect } from 'react'
import { ReactReduxContext } from './Context'
import Subscription from '../utils/Subscription'

function Provider({ store, context, children }) {
  const contextValue = useMemo(() => {
    const subscription = new Subscription(store) // 将Store生成一个订阅实例
    // NOTE:将实例的通知订阅组的方法放到stateChange来处理
    subscription.onStateChange = subscription.notifyNestedSubs
    return { store, subscription }
  }, [store])

  // NOTE:用来保存Store的State
  const previousState = useMemo(() => store.getState(), [store])

  useEffect(() => {
    const { subscription } = contextValue
    subscription.trySubscribe() // 初始化全部订阅

    // NOTE:state有改变时,执行全部provider订阅的事件
    if (previousState !== store.getState()) {
      subscription.notifyNestedSubs()
    }
    return () => {
      // NOTE:解绑和重制onStateChange,因为redux的store那边注册了该方法
      subscription.tryUnsubscribe()
      subscription.onStateChange = null
    }
  }, [contextValue, previousState])

  const Context = context || ReactReduxContext

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

export default Provider

Provide 的作用

  1. 先 new Subscription,这个是 Store 的 Subscription
  2. 将 Subscription 的方法等注册,主要用来实现订阅和更新
  3. 创建一个 Provider,用来传递和存储 Store 和 Subscription

Context

import React from 'react'
// NOTE:生成一个Context,用作全局的store,多处地方会使用,如provider,还有一些useStore等
export const ReactReduxContext = React.createContext(null)

export default ReactReduxContext

shallowEqual

const hasOwn = Object.prototype.hasOwnProperty

function is(x, y) {
  if (x === y) {
    return x !== 0 || y !== 0 || 1 / x === 1 / y
  } else {
    return x !== x && y !== y
  }
}

// NOTE:浅对比
export default function shallowEqual(objA, objB) {
  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 (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
      return false
    }
  }

  return true
}

Hooks

useStore

import { useContext } from 'react'
import { ReactReduxContext } from '../components/Context'
import { useReduxContext as useDefaultReduxContext } from './useReduxContext'

// NOTE:有传入context则使用传入,否则使用useStore里的store,并且返回store
export function createStoreHook(context = ReactReduxContext) {
  const useReduxContext =
    context === ReactReduxContext
      ? useDefaultReduxContext
      : () => useContext(context)
  return function useStore() {
    const { store } = useReduxContext()
    return store
  }
}

export const useStore = createStoreHook()

useDispatch

import { ReactReduxContext } from '../components/Context'
import { useStore as useDefaultStore, createStoreHook } from './useStore'

// NOTE:和useStore同样逻辑,如有传入context则使用传入,否则使用useStore里的store,并且返回store的dispatch
export function createDispatchHook(context = ReactReduxContext) {
  const useStore =
    context === ReactReduxContext ? useDefaultStore : createStoreHook(context)
  return function useDispatch() {
    const store = useStore()
    return store.dispatch
  }
}

export const useDispatch = createDispatchHook()

useReduxContext

import { useContext } from 'react'
import { ReactReduxContext } from '../components/Context'

// NOTE:获取ReduxdContext的Value,一般用来拿Store
export function useReduxContext() {
  const contextValue = useContext(ReactReduxContext)
  return contextValue
}

useSelector

import {
  useReducer,
  useRef,
  useEffect,
  useMemo,
  useLayoutEffect,
  useContext,
} from 'react'
import { useReduxContext as useDefaultReduxContext } from './useReduxContext'
import Subscription from '../utils/Subscription'
import { ReactReduxContext } from '../components/Context'

// NOTE:使用同构LayoutEffect
const useIsomorphicLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect

// NOTE:浅对比前后两个store的值
const refEquality = (a, b) => a === b

function useSelectorWithStoreAndSubscription(
  selector, //NOTE:state => state.test
  equalityFn,
  store,
  contextSub
) {
  // NOTE:定义一个useReducer,用来进行update
  const [, forceRender] = useReducer(s => s + 1, 0)

  // NOTE:生成一个Subscription的实例
  const subscription = useMemo(
    () => new Subscription(store, contextSub),
    [store, contextSub]
  )

  // NOTE:用来存储state和error信息
  const latestSubscriptionCallbackError = useRef()
  const latestSelector = useRef()
  const latestSelectedState = useRef()

  let selectedState

  try {
    // NOTE:如果两个selector的不相等,则重新处理,否则按上一次
    if (
      selector !== latestSelector.current ||
      latestSubscriptionCallbackError.current
    ) {
      selectedState = selector(store.getState())
    } else {
      selectedState = latestSelectedState.current
    }
  } catch (err) {
    // NOTE:在获取Store的时候,如果有问题就抛出异常
    throw new Error()
  }

  // NOTE:在执行effect时,将最新的selector存储
  useIsomorphicLayoutEffect(() => {
    latestSelector.current = selector
    latestSelectedState.current = selectedState
    latestSubscriptionCallbackError.current = undefined
  })

  // NOTE:当Store或subscription有改变的话,直接执行checkForUpdates
  useIsomorphicLayoutEffect(() => {
    // NOTE:对比前后selectState的值,全等则不做处理,有改变则将最新的值存储,并执行update的订阅
    function checkForUpdates() {
      try {
        const newSelectedState = latestSelector.current(store.getState())

        if (equalityFn(newSelectedState, latestSelectedState.current)) {
          return
        }

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

      // NOTE:update,将最新的selectedState返回
      forceRender({})
    }
    // NOTE:在原先Store的subscription上增加订阅了checkForUpdates
    subscription.onStateChange = checkForUpdates
    subscription.trySubscribe()

    /* 
      NOTE:个人疑惑,一开始因为依赖项是store和subscription,那样应该store一变就执行这个effect,
      后面想清楚,store, subscription如果不是完全的改变是不会触发,因为指针并未改变
      所以后面只是subscription去触发checkForUpdates,而不是effect
    */
    checkForUpdates()

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

  return selectedState
}

// NOTE:与其他Hooks同理
export function createSelectorHook(context = ReactReduxContext) {
  const useReduxContext =
    context === ReactReduxContext
      ? useDefaultReduxContext
      : () => useContext(context)
  return function useSelector(selector, equalityFn = refEquality) {
    // NOTE:关键点,获取当前Store的subscription和context的信息
    const { store, subscription: contextSub } = useReduxContext()

    // NOTE:等于在对当前Store进行了扩展和订阅
    return useSelectorWithStoreAndSubscription(
      selector,
      equalityFn,
      store,
      contextSub
    )
  }
}

export const useSelector = createSelectorHook()

useSelector 的执行流程:

  1. 先定义使用的 effect 和一些判断的工具函数
  2. 定义 reducer 用来进行 state 的更新
  3. new Subscription,这个是 useSelector 的 Subscription,useSelector 用在组件里面,一般就是当前组件的 Subscription
  4. 定义用来存储 state 和 error 等的变量
  5. 处理好存储 state 和 error 的场景
  6. 注册订阅和更新
  7. 返回一个处理好 Store 的 useSelector

Hooks 的 React Redux 对比以前用 Connect 简单不少,重点只要看 Provider、useSelector 和 Subscription,其他的 hooks 和 context,都是存储和创建 store 的 context,hooks 就直接使用 store 的 contextValue。

与 Redux 结合的 Flow

react-redux-hooks

Example

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

Connect()

在上已经说明 Hooks 的部分,下面主要是 Connect 部份,只说明 Connect 部份的主要代码,一些 Commons 的 Provider 就不重复说明了。

connect

import 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'

//  NOTE:将参数全部放到工厂函数中执行,拿回工厂函数的返回值
function match(arg, factories, name) {
  for (let i = factories.length - 1; i >= 0; i--) {
    const result = factories[i](arg)
    if (result) return result
  }
}

/*
  createConnect返回了一个connect,它主要将
    mapStateToPropsFactories 
    mapDispatchToPropsFactories
    mergePropsFactories
    selectorFactory
  等工厂函数设置好
 */
export function createConnect({
  connectHOC = connectAdvanced,
  mapStateToPropsFactories = defaultMapStateToPropsFactories,
  mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
  mergePropsFactories = defaultMergePropsFactories,
  selectorFactory = defaultSelectorFactory,
} = {}) {
  /*
    connect主要是返回了connectHOC,一个HOC组件用来包裹用户的components
    主要作用是接收用户传递下来的 mapStateToProps、mapDispatchToProps、mergeProps、options
    并且将mapStateToProps、mapDispatchToProps、mergeProps通过工厂函数进行处理,传递给HOC
  */
  return function connect(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    {
      pure = true,
      areStatesEqual = strictEqual,
      areOwnPropsEqual = shallowEqual,
      areStatePropsEqual = shallowEqual,
      areMergedPropsEqual = shallowEqual,
      ...extraOptions
    } = {}
  ) {
    /* NOTE:initMapStateToProps、initMapDispatchToProps、initMergeProps
      三个方法基本相同,主要用于借助工厂函数合并props、封装actions等,具体的可以看mapStateToProps,这里不对每个都进行过一遍
      然后return 一个function
    */
    const initMapStateToProps = match(
      mapStateToProps,
      mapStateToPropsFactories,
      'mapStateToProps'
    )

    const initMapDispatchToProps = match(
      mapDispatchToProps,
      mapDispatchToPropsFactories,
      'mapDispatchToProps'
    )
    const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')

    return connectHOC(selectorFactory, {
      methodName: 'connect', // 在error的时候会进行显示
      getDisplayName: name => `Connect(${name})`, // 获取组件的displayName.
      shouldHandleStateChanges: Boolean(mapStateToProps), // mapStateToProps为true,connect的组件会订阅state的改变

      // 需要用到的一些props参数和equal的参数
      initMapStateToProps,
      initMapDispatchToProps,
      initMergeProps,
      pure,
      areStatesEqual,
      areOwnPropsEqual,
      areStatePropsEqual,
      areMergedPropsEqual,
      // 其他的用户设置参数设置
      ...extraOptions,
    })
  }
}

export default createConnect()

connectAdvanced

/* 
  NOTE:原始组件的所有静态方法全部拷贝给新组件
  class Demo extends React.Component{
    render(){
      return (<div>Demo</div>)
    }
  }
  Demo.test = () => console.log('test')
  在被高阶组件包裹后会失去test,const NewDemo = Hoc(Demo)
  typeof NewDemo.test === 'undefined' // true
  hoist-non-react-statics会自动处理这种绑定: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over
*/
import hoistStatics from 'hoist-non-react-statics'

import React, {
  useContext,
  useMemo,
  useEffect,
  useLayoutEffect,
  useRef,
  useReducer,
} from 'react'
import { isValidElementType, isContextConsumer } from 'react-is'
import Subscription from '../utils/Subscription'

import { ReactReduxContext } from './Context'

const EMPTY_ARRAY = [] // 不进行更新时的state设置
const NO_SUBSCRIPTION_ARRAY = [null, null] // 不进行订阅的Subscription设置

// NOTE:创建一个Reducer,每次调用就count + 1,并将payload(latestStoreState,error)等存起来
function storeStateUpdatesReducer(state, action) {
  const [, updateCount] = state
  return [action.payload, updateCount + 1]
}

// NOTE:惰性去初始化Reducer的state
const initStateUpdates = () => [null, 0]

// NOTE:判断使用Effect还是LayoutEffect,如果同构则使用useLayoutEffect
const useIsomorphicLayoutEffect =
  typeof window?.document?.createElement !== 'undefined'
    ? useLayoutEffect
    : useEffect

//  NOTE:connectAdvanced就是返回一个function组件,wrapWithConnect,并且设置好参数信息和验证
export default function connectAdvanced(
  /*
    selectorFactory也即是finalPropsSelectorFactory,主要用来处理props
    通过options的pure来决定使用的合并props的处理方式
    pure的时候,可以直接拿结果,如果props没有改变,connectAdvanced shouldComponentUpdate可以返回false,不更新
    非pure的时候,直接拿新的props对象,进行更新
  */
  selectorFactory,
  {
    getDisplayName = name => `ConnectAdvanced(${name})`, // 获取组件的displayName
    methodName = 'connectAdvanced', // 用来显示错误信息
    renderCountProp = undefined, // 已经被废弃
    shouldHandleStateChanges = true, // 即是Boolean(mapStateToProps)
    storeKey = 'store', // 已经被废弃
    withRef = false, // 已经被废弃
    forwardRef = false, // 在组件中是否有使用forwardRef,需要用户进行设置
    context = ReactReduxContext, // 默认值ReactReduxContext,即最顶层的provider,一般存放store
    ...connectOptions // 另外一些用户设置的options
  } = {}
) {
  const Context = context

  // NOTE:包裹的组件,connect()(WrappedComponent: <Test />),
  return function wrapWithConnect(WrappedComponent) {
    // NOTE:处理好组件名、还有一些options的设置
    const wrappedComponentName =
      WrappedComponent.displayName || WrappedComponent.name || 'Component'

    const displayName = getDisplayName(wrappedComponentName)

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

    const { pure } = connectOptions

    // NOTE:用selectorFactory函数生成新的Props
    function createChildSelector(store) {
      return selectorFactory(store.dispatch, selectorFactoryOptions)
    }

    // NOTE:如果是Pure会进行优化处理,具体可看Memo
    const usePureOnlyMemo = pure ? useMemo : callback => callback()

    // NOTE:最重要看这个,按流程看,我们先从return的东西反过来看
    function ConnectFunction(props) {
      /**
       * NOTE:流程3 都是由props传下来的信息,一般业务情况下也没有传propsContext, forwardedRef
       * wrapperProps就是connect的组件,本身是否有传递的props
       *  */
      const [propsContext, forwardedRef, wrapperProps] = useMemo(() => {
        const { forwardedRef, ...wrapperProps } = props
        return [props.context, forwardedRef, wrapperProps]
      }, [props])

      /**
       * NOTE:流程2 获取使用的Context,
       * 判断是使用propsContext(props,最近一层)还是Context(全局,最顶层)
       * 一般情况下都是使用Context(全局,最顶层)
       *  */
      const ContextToUse = useMemo(() => {
        return propsContext &&
          propsContext.Consumer &&
          isContextConsumer(<propsContext.Consumer />)
          ? propsContext
          : Context
      }, [propsContext, Context])

      // NOTE:生成contextValue
      const contextValue = useContext(ContextToUse)

      // NOTE:store必须是来自props,来自上下文context
      const didStoreComeFromProps = Boolean(props.store)
      const didStoreComeFromContext =
        Boolean(contextValue) && Boolean(contextValue.store)

      const store = props.store || contextValue.store

      // NOTE:用Store传入生成props
      const childPropsSelector = useMemo(() => {
        return createChildSelector(store)
      }, [store])

      // NOTE:流程5 subscription,就是Subscription,具体可以看Subscription.js,它可以订阅和通知执行所有的订阅事件等
      const [subscription, notifyNestedSubs] = useMemo(() => {
        // NOTE:如果不需要处理stateChanges,则不增加订阅
        if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY

        // NOTE:生成一个新的Subscription,并且传入store
        const subscription = new Subscription(
          store,
          didStoreComeFromProps ? null : contextValue.subscription // 如props传下来的store,则订阅到该store里,否则增加到最顶层的provider的订阅中
        )

        // NOTE:暴露出notifyNestedSubs,并将subscription的notifyNestedSubs,bind到当前subscription
        const notifyNestedSubs =
          subscription.notifyNestedSubs.bind(subscription)

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

      /**
       * 流程4 判断是否需要返回subscription订阅
       */
      const overriddenContextValue = useMemo(() => {
        /**
         * NOTE:如果store从props传下来,那就返回contextValue,否则需要加上subscription
         * 大多数都没有从props去传递store,更多是直接connect
         * 所以主要还是加上了订阅
         */
        if (didStoreComeFromProps) {
          return contextValue
        }

        return {
          ...contextValue,
          subscription,
        }
      }, [didStoreComeFromProps, contextValue, subscription])

      // NOTE:应用了一个Reducer,用于更新,具体用法可以看官方useReducer,基本与Reducer相似
      const [
        [previousStateUpdateResult], // [previousStateUpdateResult] = [action.payload, updateCount + 1]
        forceComponentUpdateDispatch,
      ] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)

      // NOTE:判断更新结果是否有error,有则抛出error
      if (previousStateUpdateResult && previousStateUpdateResult.error) {
        throw previousStateUpdateResult.error
      }

      // NOTE:初始化定义一些参数,用来保存数据和状态
      const lastChildProps = useRef() // 最后一次child的props,包含、dispatch、router等有中间件和父组件的props
      const lastWrapperProps = useRef(wrapperProps) // 最后一次渲染组件的Props,一个是父组件传的
      const childPropsFromStoreUpdate = useRef()
      // NOTE:是否Render已经被预约,防止造成多render
      const renderIsScheduled = useRef(false)

      // NOTE:获取真实的child的props
      const actualChildProps = usePureOnlyMemo(() => {
        // NOTE:如果是从Store里面更新,并且props和上一个props相等,直接返回props,否则就创建一个新的props
        if (
          childPropsFromStoreUpdate.current &&
          wrapperProps === lastWrapperProps.current
        ) {
          return childPropsFromStoreUpdate.current
        }

        return childPropsSelector(store.getState(), wrapperProps)
      }, [store, previousStateUpdateResult, wrapperProps])

      /**
       * NOTE:流程6 当流程跑完我们按顺序看一下两个Effect
       * 该effect每次render都执行,作用在存储props,然后通知所有的订阅
       */
      useIsomorphicLayoutEffect(() => {
        // NOTE:一有更新就将props还有actualChildProps存起来
        lastWrapperProps.current = wrapperProps
        lastChildProps.current = actualChildProps
        renderIsScheduled.current = false

        // NOTE:如果props从Store里面更新,就重置childPropsFromStoreUpdate和执行全部订阅
        if (childPropsFromStoreUpdate.current) {
          childPropsFromStoreUpdate.current = null
          notifyNestedSubs()
        }
      })

      // NOTE:流程7 该Effect使用来处理是否更新
      useIsomorphicLayoutEffect(() => {
        if (!shouldHandleStateChanges) return

        // NOTE:判断是否已经取消订阅
        let didUnsubscribe = false
        // NOTE:判断是否有抛出error
        let lastThrownError = null

        // NOTE:和名字一样,作为判断是否要Update处理
        const checkForUpdates = () => {
          //NOTE:取消订阅不做处理
          if (didUnsubscribe) {
            return
          }

          // NOTE:获取store的整个状态
          const latestStoreState = store.getState()

          let newChildProps, error
          try {
            // NOTE:将整个store的state和当前组件的WrapperProps生成一个最新的childProps
            newChildProps = childPropsSelector(
              latestStoreState,
              lastWrapperProps.current
            )
          } catch (e) {
            // NOTE:看是否生成的过程中出错
            error = e
            lastThrownError = e
          }

          if (!error) {
            lastThrownError = null
          }

          // NOTE:生成的最新childProps和存起来的lastChildProps是否相等,并且会看render是否已经在进行
          if (newChildProps === lastChildProps.current) {
            if (!renderIsScheduled.current) {
              /**
               * NOTE:前后props相等而且不在render中,执行所有已经订阅的方法
               * notifyNestedSubs是new Subscription,后续的subscription.trySubscribe(),是将checkForUpdates绑定到
               * 顶层的subscription
               * 所以该方法不会造成死循环
               * */
              notifyNestedSubs()
            }
          } else {
            // NOTE:当前后props不相等,则将新的props存起来,并将renderIsScheduled设置为true,做好要update的准备
            lastChildProps.current = newChildProps
            childPropsFromStoreUpdate.current = newChildProps
            renderIsScheduled.current = true

            // NOTE:通过dispatch去update,刷新了当前这个Connect()(Components)
            forceComponentUpdateDispatch({
              type: 'STORE_UPDATED',
              payload: {
                latestStoreState,
                error,
              },
            })
          }
        }

        // NOTE:将checkForUpdates这个方法放到onStateChange,然后trySubscribe去初始化注册,将onStateChange注册到store的订阅中
        subscription.onStateChange = checkForUpdates
        subscription.trySubscribe()

        // NOTE:首次的时候进行一次检查更新,确保准确性
        checkForUpdates()

        // NOTE:解绑订阅的事件
        const unsubscribeWrapper = () => {
          didUnsubscribe = true
          subscription.tryUnsubscribe()
          subscription.onStateChange = null

          if (lastThrownError) {
            throw lastThrownError
          }
        }

        return unsubscribeWrapper
      }, [store, subscription, childPropsSelector])

      // NOTE:对组件进行memo优化
      const renderedWrappedComponent = useMemo(
        () => <WrappedComponent {...actualChildProps} ref={forwardedRef} />,
        [forwardedRef, WrappedComponent, actualChildProps]
      )

      // NOTE:流程1-整个函数的返回,加上memo进行优化
      const renderedChild = useMemo(() => {
        /*
          NOTE:流程1.1-判断是否要处理state的改变
          即是Boolean(mapStateToProps),如connect()(),则不需要处理state改变
          不需要处理的直接将组件render,需要的则包裹一个Provider
        */
        if (shouldHandleStateChanges) {
          return (
            <ContextToUse.Provider value={overriddenContextValue}>
              {renderedWrappedComponent}
            </ContextToUse.Provider>
          )
        }
        return renderedWrappedComponent
      }, [ContextToUse, renderedWrappedComponent, overriddenContextValue])

      return renderedChild
    }

    // NOTE:Connect的参数配置,pure就加上memo,Connect即是ConnectFunction
    const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction

    Connect.WrappedComponent = WrappedComponent
    Connect.displayName = displayName

    return hoistStatics(Connect, WrappedComponent)
  }
}

Flow

react-redux-connect

Conclusion

对比 Hooks 的 useSelector 代码,Connect 的代码更加复杂,需要用工厂函数 (initMapStateToProps、initMapDispatchToProps、initMergeProps) 去合并 props、封装 actions,而且因为代码量更多,它的代码体积也会更大,与 React 未来的新特性结合也没有那么好,所以建议使用 Hooks,方便维护和使用新 React 特性。

Powered by Bobolo

Copyright © Bobolo Blog 2021