react-redux原始碼解析

aGod發表於2018-05-23

react-redux原始碼解析

react-redux作為大型react應用中連結react和redux的連結庫,在很多reat專案中廣泛應用。作為開發者的同學們瞭解reac-redux的原理和原始碼對我們的能力提升具有一定的幫助,所謂知其然知其所以然。

預備知識

react context

react 高階元件(HOC)

react-redux(建議不知道connectAdvanced API,connectd的四個引數不是特別熟的同學先去簡單過一下)

準備工作

老規矩github 找到 react-redux, clone master

  本文版本: 6.0.0-beta.3
複製程式碼

當然你也可以選擇看筆者的gitHub專案, 其中包含redux和react-redux的原始碼及詳細的註釋

開始擼碼

開啟專案之後,其他的不多說了,我們直接看src裡面的核心程式碼,從index.js 開始(哦, 這裡插一嘴,react-redux是基於react和redux實現的,可以看一下package.json裡面的peerDependencies)

react-redux原始碼解析

一如既往的簡單, 暴露出的Provider, connectAdvanced, connect 都有對應的api, 我們先來看看Provider的實現。

Provider實現

我們先想一想reac-redux都幹了什麼,簡單來說用 < Provider >< /Provider>包裝了根元件, 把 store 作為 props 傳遞到每一個被 connect() 包裝的元件,其中state to props的計算過程做了優化。 react本身倡導props的自上而下單項傳遞,但是也提供context支援跨層級傳遞,到這裡,聰明的你是不是聯想到了provider與context有關呢,直接看?原始碼相關注釋以新增

建議先看Provider.propTypes提供了那些props -> constructor -> render -> 宣告週期鉤子方法 -> 總結

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { ReactReduxContext } from './Context'

class Provider extends Component {
  constructor(props) {
    super(props)

    // 獲取store
    const { store } = props

    // 初始化state, storeState為初始的redux state
    this.state = {
      storeState: store.getState(),
      // 儲存init store
      store
    }
  }

  componentDidMount() {
    // 檔案收索_isMounted, 共三處, componentWillUnmount中賦值為false
    // 先假設為標記componentDidMount -> componentWillUnmount中
    this._isMounted = true
    // 來看下subscribe
    this.subscribe()
  }

  componentWillUnmount() {
    // 取消監聽subscribe
    if (this.unsubscribe) this.unsubscribe()
    // 生命週期結束this._isMounted 賦值為false =>_isMounted假設成立✅
    this._isMounted = false
  }

  componentDidUpdate(prevProps) {
    // 如果更新之後,store的引用發生變化
    if (this.props.store !== prevProps.store) {
      // 如果存在監聽則取消
      if (this.unsubscribe) this.unsubscribe()
      // 更新storeState
      this.subscribe()
    }
    /*
    * 在我的理解中redux的store應該是不變的,主要就是為了提供"全域性變數state"
    *   既然redux作者這樣寫,我們來思考一下為什麼
    *   一般store應該是這樣 let store = createStore(rootReducer, initState,  applyMiddleware(xxx));
    *   this.props.store !== prevProps.store說明全域性的store發生了變化。既createStore方法的重新呼叫,
    *   閱讀過redux原始碼的同學應該知道, 既然是新的createStore, "全域性的state"會發生變化,不再是之前的記憶體空間
    *   所有這裡再次呼叫subscribe更新state裡面的storeState
    * 
    * * */

  }

  // 使用store.subscribe方法,保證storeState的最新
  subscribe() {
    const { store } = this.props
    // 監聽subscribe
    this.unsubscribe = store.subscribe(() => {
     // 獲取最新的state賦值給newStoreState
      const newStoreState = store.getState()
      // 不在本次生命週期中return
      if (!this._isMounted) {
        return
      }

      this.setState(providerState => {
        // If the value is the same, skip the unnecessary state update.
        // 如果state是相同的引用, 直接跳過state的更新
        if (providerState.storeState === newStoreState) {
          return null
        }

        // 更新當前storeState
        return { storeState: newStoreState }
      })
    })
    
    const postMountStoreState = store.getState()
    if (postMountStoreState !== this.state.storeState) {
      this.setState({ storeState: postMountStoreState })
    }
  }

  render() {
    // ReactReduxContext為預設context, 點過去看一下預設值。 看 -> context.js檔案,createContext引數是null
    const Context = this.props.context || ReactReduxContext

    // value 為this.state
    return (
      <Context.Provider value={this.state}>
        {this.props.children}
      </Context.Provider>
    )
  }
}

Provider.propTypes = {
  // 接受store做為props, 並規定store object中的必要方法,既redux createStore的返回物件
  store: PropTypes.shape({
    subscribe: PropTypes.func.isRequired,
    dispatch: PropTypes.func.isRequired,
    getState: PropTypes.func.isRequired
  }),
  // 接收自定義context
  context: PropTypes.object,
  // children, 通常為根容器<app />
  children: PropTypes.any
}

export default Provider

/**
 *  provider總結
 * 
 *  provider是react-redux提供是react應用的入口元件, 一般為頂層元件<Provider store={store}><App /></Provider>
 *  使用react的context傳遞store, 老版本的provider用的getChildContext方法, 隨著react context的api改變,生產者消費者模式的新api尤然而生,因此
 *  provider的原始碼已經重構, 提供的context為{ store, storeState: state},state儲存為最新
 * 
 * * */

複製程式碼

Context.js 提供全域性的初始化ReactReduxContext

  import React from 'react'

// defaultValue 為 null
export const ReactReduxContext = React.createContext(null)

// export出來,以便提供給provider和consumer使用
export default ReactReduxContext

複製程式碼

connect 實現

先回顧一下connect的使用形式:

connect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options])(<Xxx / >)
複製程式碼

這裡還要了解一下connectAdvanced(selectorFactory, [connectOptions])方法,它是一個將 React 元件連線到 Redux store 的函式。這個函式是 connect() 的基礎。

由於connect方法內引用檔案,function及function的巢狀比較多,如果看官老爺不想過於在意細節,可以直接看一下connect總結

開始看connect原始碼 -> connect.js,簡單看看結構和引用了哪些東西 ->然後我們從export default開始->遇到呼叫的function再去具體看實現了哪些功能

connect.js

/**
 *  先回顧一下connect的引數connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
 *  作用:連線 React 元件與 Redux store
 *  mapDispatchToProps(dispatch, [ownProps])
 * 
 *  react-redux 暴露的api, connectAdvanced(selectorFactory, [connectOptions])
 *  連線react元件和redux的store, 這個引數是connect的基礎
*/
import connectAdvanced from '../components/connectAdvanced'
import shallowEqual from '../utils/shallowEqual'
import defaultMapDispatchToPropsFactories from './mapDispatchToProps'
// mapStateToProps(state, ownProps)
// 只要 Redux store 發生改變,mapStateToProps 函式就會被呼叫, 
// 或者如果有ownProps引數元件接收到新的props,mapStateToProps同樣會被呼叫
import defaultMapStateToPropsFactories from './mapStateToProps'
// mapStateToProps() 與 mapDispatchToProps() 的執行結果和元件自身的 props 將傳入到這個回撥函式中
import defaultMergePropsFactories from './mergeProps'
// 定製 connector 如 pure = true 等等等...
import defaultSelectorFactory from './selectorFactory'

/**
 *
 * 這裡先以mapStatetoProps為例子, 其他的同理,簡單瞭解後續用到再看
 * @param {*} arg  使用時傳進來的mapStatetoProps
 * @param {*} factories  array[function, function]
 * 預設工廠defaultMapStateToPropsFactories, [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]
 * @param {*} name  string
 * @returns  function
 */
function match(arg, factories, name) {
  // 後到前遍歷factories
  for (let i = factories.length - 1; i >= 0; i--) {
    // 呼叫factories, 返回值賦值給result, 去看 -> mapStateToProps.js
    const result = factories[i](arg)
    // result為true返回result, result為(dispatch, options) => function(){...}的function
    if (result) return result
  }
  // 不符合connect方法規則throw Error
  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
}

// 暴露createConnect, 想必肯定會返回function因為connect是個function
export function createConnect({
  // 一些帶有預設值的引數, 我們看下面, 具體用到了在看?
  connectHOC = connectAdvanced, // connectAdvanced(selectorFactory, [connectOptions])
  mapStateToPropsFactories = defaultMapStateToPropsFactories, // array[function, function]
  mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
  mergePropsFactories = defaultMergePropsFactories,
  selectorFactory = defaultSelectorFactory
} = {}) {
  // 返回的connect function
  return function connect(
    // connect的四個可選引數
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    // 配置引數
    {
      pure = true, // 是否就行淺比較的配置
      // 判斷是否相同引用的function
      areStatesEqual = strictEqual, 
      // shallowEqual 詳情看下面?
      areOwnPropsEqual = shallowEqual,
      areStatePropsEqual = shallowEqual,
      areMergedPropsEqual = shallowEqual,
      // 其他配置項
      ...extraOptions
    } = {}
  ) {
    // mapStateToProps初始化
    const initMapStateToProps = match(
      // 使用時傳遞的mapStateToProps function
      mapStateToProps,
      // 預設值 -> 先看match方法, 然後我們來看mapStateToProps.js
      mapStateToPropsFactories,
      'mapStateToProps'
    )
    // mapDispatchToProps初始化
    const initMapDispatchToProps = match(
      mapDispatchToProps,
      mapDispatchToPropsFactories,
      'mapDispatchToProps'
    )
    // mergeProps的初始化
    const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')

    // return connectHOC function,將 React 元件連線到 Redux store 的函式
    // 先來看看他的引數
    // selectorFactory函式返回一個selector函式,根據store state, 展示型元件props,和dispatch計算得到新props,最後注入容器元件
    // selectorFactory  -> defaultSelectorFactory
    // 其實很熟悉react-redux api的同學應該很熟悉connectHOC的引數, 因為他就是connectAdvanced方法啊, 建議先看看api
    return connectHOC(selectorFactory, {
      // 用於錯位資訊
      methodName: 'connect',
      // 用Connect包裝getDisplayName
      getDisplayName: name => `Connect(${name})`,
      // mapStateToProps是否為undefined,shouldHandleStateChanges為false則不監聽store state
      shouldHandleStateChanges: Boolean(mapStateToProps),

      // selectorFactory需要的幾個引數
      initMapStateToProps,     // (dispatch, options) => initProxySelector(dispatch, { displayName }){...}
      initMapDispatchToProps,
      initMergeProps,
      pure, // 預設true
      // strictEqual, 這裡很容易想到用於判斷this.state是不是都一份引用
      areStatesEqual,  
      // shallowEqual淺比較
      // 插個題外話,熟悉react PureComponent的同學應該可以快速反應過來!shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState)
      // 不熟悉的同學看過來 -> shallowEqual.js
      areOwnPropsEqual,  
      areStatePropsEqual,
      areMergedPropsEqual,
      // 容錯處理, 其他的配置項
      ...extraOptions
    })
  }
}

// connect方法 直接呼叫createConnect
export default createConnect()


複製程式碼

mapStateToProps.js

import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'

// 當mapStateToProps為function時呼叫wrapMapToPropsFunc
export function whenMapStateToPropsIsFunction(mapStateToProps) {
  return typeof mapStateToProps === 'function'
    // 看wrapMapToPropsFunc -> wrapMapToProps.js
    ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
    : undefined
}

// match中的陣列倒著遍歷先看這裡
// 容錯處理, 判斷是否傳遞mapStateToProps引數
export function whenMapStateToPropsIsMissing(mapStateToProps) {
  // 如果傳遞了mapStateToProps引數且!mapStateToProps = true返回undefined,此時result為undefined,無效呼叫
  // 沒有傳遞mapStateToProps引數或mapStateToProps=false -> wrapMapToPropsConstant
  return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined
}

// export default是array,元素為function
export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]


/**
 * 小結:
 * 
 * mapstateToprops引數可執行時呼叫wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
 * 不傳遞mapstateToprops引數時 wrapMapToPropsConstant(() => ({}))
 * 我們接著刨根問底 看->wrapMapToProps.js
 * 
 * * */

複製程式碼

wrapMapToProps.js

import verifyPlainObject from '../utils/verifyPlainObject'

/**
 *
 * @export  mapstateToprops為undefined呼叫
 * @param {*} getConstant  () => ({})
 * @returns  function initConstantSelector(dispatch, options)
 */
export function wrapMapToPropsConstant(getConstant) {
  // 返回 initConstantSelector
  return function initConstantSelector(dispatch, options) {
    const constant = getConstant(dispatch, options) 

    function constantSelector() {
      return constant
    }
    constantSelector.dependsOnOwnProps = false
    return constantSelector
  }
}

// 用來判斷是否存在ownProps 
// mapStateToProps(state, [ownProps])
export function getDependsOnOwnProps(mapToProps) {
  // 不是第一次呼叫直接返回Boolean
  return mapToProps.dependsOnOwnProps !== null &&
    mapToProps.dependsOnOwnProps !== undefined
    ? Boolean(mapToProps.dependsOnOwnProps)
    // 第一次呼叫時mapToProps的dependsOnOwnProps為undefined,直接判斷引數個數
    : mapToProps.length !== 1  
}

/**
 * @export  mapstateToprops傳遞時呼叫
 * @param {*} mapToProps  使用connect是傳遞的mapStateToProprs
 * @param {*} methodName  名稱  methodName = 'mapStatetoProps' || 'mapDispatchToProps'
 * @returns  返回initProxySelector(dispatch, { displayName })
 */
export function wrapMapToPropsFunc(mapToProps, methodName) {
  // 終於找到你!! 返回initProxySelector function, 這個返回值會賦值給initMapStateToProps(當然還有initDispatchToProps)
  return function initProxySelector(dispatch, { displayName }) {
    // 定義proxy function,且作為返回值
    const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
      return proxy.dependsOnOwnProps  // mapStateToProps計算是否依賴元件的props
        ? proxy.mapToProps(stateOrDispatch, ownProps) //  返回proxy.mapToProps,繼續看一下他是什麼鬼?
        : proxy.mapToProps(stateOrDispatch)
    }
    // dependsOnOwnProps標記執行依賴元件的props為true
    proxy.dependsOnOwnProps = true

    // detectFactoryAndVerify為返回的function
    // 梳理一下,目前呼叫鏈是這樣的
    // const initMapStateToProps = initProxySelector(dispatch, { displayName })=>
    //                             mapToPropsProxy(stateOrDispatch, ownProps) => detectFactoryAndVerify(stateOrDispatch, ownProps)
    // detectFactoryAndVerify賦值給proxy.mapToProps
    // 第一次呼叫 mapToPropsProxy時返回detectFactoryAndVerify(stateOrDispatch, ownProps)
    proxy.mapToProps = function detectFactoryAndVerify(
      stateOrDispatch,
      ownProps
    ) {
      // 呼叫的時候 mapToPropsfunction 賦值給 proxy.mapToProps
      // 也就是第一次除了呼叫到proxy.mapToProps之後, 以後在呼叫到proxy.mapToProps的時候則使用傳遞的mapToProps function
      proxy.mapToProps = mapToProps
      // 重新判斷 dependsOnOwnProps(第一次預設true)
      proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
      // 定義props為proxy(stateOrDispatch, ownProps)
      // 先看下執行順序 第一次呼叫initProxySelector() => proxy() => 
      // 此時 proxy.mapToProps = detectFactoryAndVerify()
      // 再次呼叫 proxy(stateOrDispatch, ownProps)時 返回值為傳遞的mapToProps(...args),也就是我們react元件需要的props
      let props = proxy(stateOrDispatch, ownProps)

      // 如果props為function再次執行
      if (typeof props === 'function') {
        proxy.mapToProps = props
        proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
        props = proxy(stateOrDispatch, ownProps)
      }

      // 非production環境檢查
      if (process.env.NODE_ENV !== 'production')
        // verifyPlainObject是utils方法, 如果不是純物件,丟擲warning
        verifyPlainObject(props, displayName, methodName)

      // 返回最終的props
      return props
    }

    return proxy
  }
}

複製程式碼

mergeProps.js和mapDisptchToPtops.js同理,返回一個array[function,function],對引數做容錯處理,返回一個initProxySelector function() => proxy => props, 具體細節後面介紹

shallowEqual

shallowEqual通常用來對object做淺比較,經常會出現在react應用中, 配合shouldComponentUpdate做效能優化, 如果熟悉PureComponent原理同學,那應該知道這段程式碼

!shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState)
複製程式碼

shallowEqual.js

// 物件自身屬性中是否具有指定的屬性
const hasOwn = Object.prototype.hasOwnProperty

// 判斷兩個值是否是相同的值
function is(x, y) {
   // SameValue algorithm
   if (x === y) { 
    return x !== 0 || y !== 0 || 1 / x === 1 / y;
  } else {
    return x !== x && y !== y;
  }
}

// 淺比較 只會比較到兩個物件的 ownProperty 是否符合 Object.is 判等,不會遞迴地去深層比較
//    shallowEqual({x:{}},{x:{}}) // false
//    shallowEqual({x:1},{x:1}) // true
export default function shallowEqual(objA, objB) {
  // 相同值直接返回true,shallowEqual返回值為boolean
  if (is(objA, objB)) return true

  // 在objA和objB不是相同值的前提下, 如果objA,objB為null或非object可以判定返回false
  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false
  }

  // 定義objA,objB的key陣列
  const keysA = Object.keys(objA)
  const keysB = Object.keys(objB)

  // length不同說明object不同
  if (keysA.length !== keysB.length) return false

  // 迴圈遍歷keysA
  for (let i = 0; i < keysA.length; i++) {
    // 如果objB不含有objA的key且objA與objB的value不同, 返回false
    if (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
      return false
    }
  }

  // 如果通過了for迴圈, 說明objA, objB中的第一層key和value都相同,恭喜通過
  return true
}

複製程式碼

connect總結

由於connect函式設計的方法過多,我們在這裡簡化一下connect的程式碼(以下為原始碼的精簡虛擬碼)

  // 首先為以引數的形式為connect注入一些方法
export function createConnect({
 ...
  // connectAdvanced為react-redux暴露出的api
  connectHOC = connectAdvanced,
  ...
} = {}) {
  // connect方法
  return function connect(
    // 接受的四個引數
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    {
      pure = true,  // 是否就行淺比較的配置
      strictEqual,  // 判斷object引用, strictEqual(a, b)=> a === b
      shallowEqual, // 淺比較,上面介紹了
      ...extraOptions  // 其他配置項
    } = {}
  ) {
    // 一系列的方法執行,對三個引數的型別做了容錯處理
    // 分別初始化了各自的引數mapStateToProps,mapDispatchToProps,mergeProps,注入了一些內部的預設引數和方法
    // 他們大致是這樣的function: 
    // (dispatch, options) => initProxySelector() => mapToPropsProxy() => props
    const initMapStateToProps = match(...args)
    const initMapDispatchToProps = match(...args)
    const initMergeProps = match(...args)
    // 返回值由執行connectAdvanced獲取,並傳入初始化的initMapStateToProps等引數和pure等配置項
    return connectAdvanced(selectorFactory, {
      initMapStateToProps,
      initMapDispatchToProps,
      initMergeProps,
      pure,
      ...extraOptions
    })
  }
}

 // 直接執行createConnect方法返回connect
export default createConnect()
複製程式碼

connectAdvanced高階函式

從connect()(<A/ >)中不難看出connect的返回值為一個高階元件,包裝當前的react元件,返回一個增強props的元件,在執行connect時會執行connectAdvanced,接下來我們不得不看下connectAdvanced方法的實現,先介紹一下connectAdvanced -> ?看註釋

connectAdvanced.js

// hoist-non-react-statics元件,這個元件會自動把所有繫結在物件上的非React靜態(static)方法都繫結到新的物件上
import hoistStatics from 'hoist-non-react-statics'
// 提示資訊外掛
import invariant from 'invariant'
import React, { Component, PureComponent } from 'react'
import { isValidElementType } from 'react-is'
// context
import { ReactReduxContext } from './Context'

/**
 *
 * @export  介紹下connectAdvanced
 *  connect() 的基礎,真正將 React 元件連線到 Redux store 的函式
 * @param {*} selectorFactory function  selectorFactory(dispatch, factoryOptions)
 *  初始化選擇器函式,該選擇器函式是在 connector 元件需要重新計算一個新的 props 時呼叫
 *  作為 store 的 state 改變或者接收到一個新的 props 的結果。selector 的結果應該是一個普通物件,
 *  作為被包裹的元件的 props 傳遞。如果連續呼叫 selector 都返回與上一次呼叫相同的物件(===),
 *  則不會重新渲染該元件。selector 的責任是在適當的時候返回以前的物件。
 * @param {*} [{
 *     getDisplayName = name => `ConnectAdvanced(${name})`,  DisplayName
 *     methodName = 'connectAdvanced',
 *     renderCountProp = undefined,
 *     shouldHandleStateChanges = true,
 *     storeKey = 'store',
 *     withRef = false,
 *     context = ReactReduxContext,
 *     ...connectOptions
 *   }={}]
 * @returns
 */
export default function connectAdvanced(
   // function, 使用的時候在介紹selectorFactory.js
  selectorFactory,
  // options object:
  {
    // 被包裹的元件的 DisplayName 屬性
    getDisplayName = name => `ConnectAdvanced(${name})`,
    // 用於顯示錯誤訊息
    methodName = 'connectAdvanced',
    // 元件是否訂閱 redux store 的 state 更改
    shouldHandleStateChanges = true,
    renderCountProp = undefined, // 傳遞給內部元件的props鍵,表示render方法呼叫次數
    // 可以獲取 store 的 props/context key
    storeKey = 'store',
    // 如果為 true,則將一個引用儲存到被包裹的元件例項中,並通過 getWrappedInstance() 方法使其可用
    withRef = false,
    // 看到這個變數我的第一反應是ref的轉發, 不瞭解的同學去看一下React.forwardRef()
    forwardRef = false,
    // provider的ReactReduxContext方法
    context = ReactReduxContext,
    ...connectOptions
  } = {}
) {
  // invariant 一個只在development環境的error
  // When process.env.NODE_ENV is not production, the message is required.
  // 這幾個配置引數別使用的時候會有warnning,直接跳過這部分
  invariant(
    renderCountProp === undefined,
    `renderCountProp is removed. render counting is built into the latest React dev tools profiling extension`
  )

  invariant(
    !withRef,
    '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'

  invariant(
    storeKey === 'store',
    'storeKey has been removed and does not do anything. ' +
      customStoreWarningMessage
  )

  // 定義Context
  const Context = context

  // 返回react高階元件,  WrappedComponent既包裝的react元件
  return function wrapWithConnect(WrappedComponent) {
    // 引數檢驗
    if (process.env.NODE_ENV !== 'production') {
      invariant(
        isValidElementType(WrappedComponent),
        `You must pass a component to the function returned by ` +
          `${methodName}. Instead received ${JSON.stringify(WrappedComponent)}`
      )
    }

    // 元件的displayName,預設Component
    const wrappedComponentName =
      WrappedComponent.displayName || WrappedComponent.name || 'Component'

    // 拼接下displayName
    const displayName = getDisplayName(wrappedComponentName)

    // 定義selectorFactoryOptions物件,包含了connect和connectAdvanced的所有引數
    const selectorFactoryOptions = {
      // connectOptions為initMapStateToProps,initMapDispatchToProps,pure等引數
      ...connectOptions,
      getDisplayName,
      methodName,
      renderCountProp,
      shouldHandleStateChanges,
      storeKey,
      displayName,
      wrappedComponentName,
      WrappedComponent
    }

    // pure決定shouldComponentUpdate是否進行shwoEqual
    const { pure } = connectOptions

    // Component賦值給OuterBaseComponent, 用react高階的繼承
    let OuterBaseComponent = Component
    // 定義FinalWrappedComponent
    let FinalWrappedComponent = WrappedComponent

    if (pure) {
      // 為true用PureComponent
      OuterBaseComponent = PureComponent
    }
    // 接下來直接看 class Connect extends OuterBaseComponent

// 
function makeDerivedPropsSelector() {
      // 定義變數, 用來儲存上一次makeDerivedPropsSelector中的值
      // 變數語義化我們不難猜出意義
      let lastProps
      let lastState
      let lastDerivedProps
      let lastStore
      let sourceSelector
      return function selectDerivedProps(state, props, store) {  // props為父元件的props
        // pure為true時,props和state的引用都沒有變化, 直接返回lastDerivedProps(第一次肯定不會成立)
        // 這裡不難理解, 就是要求你用"純"的state和props
        if (pure && lastProps === props && lastState === state) {
          return lastDerivedProps
        }

        if (store !== lastStore) {
          // store賦值lastStore, 更新lastStore
          // 除第一次呼叫外一般不會
          lastStore = store
          // selectorFactory為connect傳入的function,預設值來自selsctorFactory.js的export default
          sourceSelector = selectorFactory(
            store.dispatch,
            selectorFactoryOptions  // 所有引數集合
          )
        }

        // props賦值給lastProps
        lastProps = props
        // state賦值給lastState
        lastState = state

        // 呼叫sourceSelector參入redux state和props, 得到最新的props
        // 不難看出selectorFactory的返回值為一個function, 目前我們可以猜測到
        // selsctorFactory.js的export default function 大體結構是這樣(dispatch, selectorFactoryOptions)=>(state, props) => newProps
        const nextProps = sourceSelector(state, props)

        // 新舊props引用相同
        if (lastDerivedProps === nextProps) {
          // 直接返回
          return lastDerivedProps
        }
        // 新舊props引用不相同, nextProps賦值給lastDerivedProps
        lastDerivedProps = nextProps
        // 返回lastDerivedProps
        return lastDerivedProps
        // 最後我們去看selsctorFactory.js到底如何合併的state和props
      }
    }

    function makeChildElementSelector() {
      // 定義props, ref, element變數
      let lastChildProps, lastForwardRef, lastChildElement
      // 返回function
      return function selectChildElement(childProps, forwardRef) {
        // 判斷新舊props, hre, elelment是否相同
        if (childProps !== lastChildProps || forwardRef !== lastForwardRef) {
          // 如果不同重新賦值
          lastChildProps = childProps
          lastForwardRef = forwardRef
          lastChildElement = (
            // return FinalWrappedComponent, 改變props和ref
            <FinalWrappedComponent {...childProps} ref={forwardRef} />
          )
        }
        // react元件
        return lastChildElement
      }
    }

    class Connect extends OuterBaseComponent {
      constructor(props) {
        super(props)
        invariant(
          forwardRef ? !props.wrapperProps[storeKey] : !props[storeKey],
          'Passing redux store in props has been removed and does not do anything. ' +
            customStoreWarningMessage
        )
        // 新增selectDerivedProps和selectChildElement方法
        // selectDerivedProps為function是makeDerivedPropsSelector的返回值
        this.selectDerivedProps = makeDerivedPropsSelector()
        // selectChildElement為function
        this.selectChildElement = makeChildElementSelector()
        // bind this
        this.renderWrappedComponent = this.renderWrappedComponent.bind(this)
      }

      // value為context,既provider中的{storeState: store.getState(),store}
      renderWrappedComponent(value) {
        invariant(
          value,
          `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.`
        )

        // 獲取redux state和store
        const { storeState, store } = value

        // 定義wrapperProps為this.props
        let wrapperProps = this.props
        let forwardedRef
        // forwardRef為真時, Connect元件提供了forwardedRef = {ref}
        if (forwardRef) {
          // wrapperProps為props中的wrapperProps
          wrapperProps = this.props.wrapperProps
          // forwardedRef賦值為props的forwardedRef, 傳遞的是ref
          // 用於傳遞給子元件WrappedComponent既let FinalWrappedComponent = WrappedComponent中的FinalWrappedComponent
          forwardedRef = this.props.forwardedRef
        }

        // 匯出props
        let derivedProps = this.selectDerivedProps(
          storeState,
          wrapperProps,
          store
        )

        // 返回最終的元件,傳入最終的props和ref -> 看selectChildElement發放
        return this.selectChildElement(derivedProps, forwardedRef)
      }

      render() {
        // 預設情況下公用的ReactReduxContext
        const ContextToUse = this.props.context || Context

        return (
          // <Privoder />的消費者
          <ContextToUse.Consumer>
            {this.renderWrappedComponent}
          </ContextToUse.Consumer>
        )
      }
    }

    // 相當於外掛
    // 包裝的元件賦值給Connect.WrappedComponent
    Connect.WrappedComponent = WrappedComponent
    // 新增displayName
    Connect.displayName = displayName

    // forwardRef為true時
    if (forwardRef) {
      // 使用react.forwardRef為connect生成的元件的父元件提供孫子(傳遞給connect的元件)元件
      const forwarded = React.forwardRef(function forwardConnectRef(
        props,
        ref
      ) {
        return <Connect wrapperProps={props} forwardedRef={ref} />
      })

      // 此時connect()(<xx />)的生成元件為forwarded, 從新掛載displayName和WrappedComponent
      forwarded.displayName = displayName
      forwarded.WrappedComponent = WrappedComponent
      return hoistStatics(forwarded, WrappedComponent)
    }

    // 將子元件的非React的static(靜態)屬性或方法合併到父元件
    // 返回擴充過props屬性的Connect元件
    return hoistStatics(Connect, WrappedComponent)
  }
}

複製程式碼

connectAdvanced返回一個react高階元件, 根據pure,forwardRef配置決定是否採用PureComponent和ref的轉移, 通過selectDerivedProps放法生成最終的props,傳遞給最終返回的react元件,最後我們去看selsctorFactory.js到底如何合併的state和props

selectorFactory

selectorFactory函式返回一個selector函式,根據store state, 展示型元件props和dispatch計算得到新props,最後注入容器元件中

selectorFactory.js實現

import verifySubselectors from './verifySubselectors'

export function impureFinalPropsSelectorFactory(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch
) {
  return function impureFinalPropsSelector(state, ownProps) {
    // 執行mergePropsProxy,返回修改後的props
    return mergeProps(
      mapStateToProps(state, ownProps),  // mapStateToProps執行結果
      mapDispatchToProps(dispatch, ownProps), // mapDispatchToProps的執行結果
      ownProps // 自身的props
    )
  }
}

export function pureFinalPropsSelectorFactory(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch,
  // areStatesEqual判斷是否是相同的引用
  // areOwnPropsEqual, areStatePropsEqual為shallowEqual
  { areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
  // hasRunAtLeastOnce標記第一次執行
  // 先看return的function,再看其他的function都做了什麼
  let hasRunAtLeastOnce = false
  let state
  let ownProps
  let stateProps
  let dispatchProps
  let mergedProps

  // 第一次執行時
  function handleFirstCall(firstState, firstOwnProps) {
    // 直接賦值以下結果
    state = firstState
    ownProps = firstOwnProps
    stateProps = mapStateToProps(state, ownProps)
    dispatchProps = mapDispatchToProps(dispatch, ownProps)
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    // hasRunAtLeastOnce標記為true
    hasRunAtLeastOnce = true
    // 返回mergedProps
    return mergedProps
  }

  function handleNewPropsAndNewState() {
    // 獲取當前新的的state
    stateProps = mapStateToProps(state, ownProps)

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

    // 返回mergedProps function內部為新的object
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }

  // 自身props改變
  function handleNewProps() {
    // dependsOnOwnProps之前介紹過,判斷是否有第一個引數ownprops
    // 如果存在需要重新執行,獲取新的stateProps和mapDispatchToProps,因為自身的props改變了
    if (mapStateToProps.dependsOnOwnProps)
      stateProps = mapStateToProps(state, ownProps)

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

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

  // redux state改變
  function handleNewState() {
    const nextStateProps = mapStateToProps(state, ownProps)
    // 淺比較nextStateProps和stateProps
    const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
    // 更新stateProps
    stateProps = nextStateProps

    //statePropsChanged為ture,淺比較失敗,mergedProps需要重新計算,mergedProps返回新物件
    if (statePropsChanged)
      mergedProps = mergeProps(stateProps, dispatchProps, ownProps)

    return mergedProps
  }

  function handleSubsequentCalls(nextState, nextOwnProps) {
    // 執行的時候ownProps淺比較
    const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
    // 比較redux state的引用
    const stateChanged = !areStatesEqual(nextState, state)
    // nextState賦值給state
    state = nextState
    // nextOwnProps賦值給onwProps
    ownProps = nextOwnProps

    // props && state change
    // 看不同情況對應的return function
    if (propsChanged && stateChanged) return handleNewPropsAndNewState()
    // props change
    if (propsChanged) return handleNewProps()
    // state change
    if (stateChanged) return handleNewState()
    // propsChanged, stateChanged為true認為props,state沒有改變,return mergedProps
    return mergedProps
  }

  return function pureFinalPropsSelector(nextState, nextOwnProps) {  // state props
    return hasRunAtLeastOnce // 預設值為false
      ? handleSubsequentCalls(nextState, nextOwnProps)
      : handleFirstCall(nextState, nextOwnProps)
  }
}

// 找到export default
/**
 *
 *
 * @export
 * @param {*} dispatch // store.dispatch
 * @param {*} { 
 *    initMapStateToProps // 結構initProxySelector(dispatch, { displayName }) => proxy
 *    initMapDispatchToProps, // 結構 initMergePropsProxy(dispatch, options) => mergePropsProxy 
 *    initMergeProps, 
 *    ...options    其他配置
 *  }
 * @returns  selectorFactory function 
 */
// finalPropsSelectorFactory和我們的設想結構一致
export default function finalPropsSelectorFactory(
  dispatch,
  
  { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }
) {
  // 呼叫initProxySelector得到proxy function, proxy包含mapToProps, dependsOnOwnProps屬性
  const mapStateToProps = initMapStateToProps(dispatch, options)
  const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
  // mergePropsProxy為function
  // 返回值為connect(mapstate,mapdispatch,function mergeProps(){})()中mergeProps的返回值
  const mergeProps = initMergeProps(dispatch, options)

  // 非production環境檢驗 mapStateToProps,mapDispatchToProps,mergeProps
  if (process.env.NODE_ENV !== 'production') {
    verifySubselectors(
      mapStateToProps,
      mapDispatchToProps,
      mergeProps,
      options.displayName
    )
  }

  // pure為true時表示selectorFactory的返回值快取, 根據當前的redux state和ownProps的變化儘量做最出小的改變
  // 詳情看pureFinalPropsSelectorFactory
  // 否則返回新物件
  const selectorFactory = options.pure
    ? pureFinalPropsSelectorFactory
    : impureFinalPropsSelectorFactory

  // 執行selectorFactory
  // selectorFactory為工廠函式,返回selector
  return selectorFactory(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps, // function mergePropsProxy
    dispatch,
    options
  )
}

複製程式碼

mergedProps實現

import verifyPlainObject from '../utils/verifyPlainObject'

export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
  // 返回一個新物件
  return { ...ownProps, ...stateProps, ...dispatchProps }
}

export function wrapMergePropsFunc(mergeProps) {
  // initMergeProps
  return function initMergePropsProxy(
    dispatch,
    { displayName, pure, areMergedPropsEqual }
  ) {
    // 第一次執行,設定為false
    let hasRunOnce = false
    let mergedProps

    return function mergePropsProxy(stateProps, dispatchProps, ownProps) {
      // mergeProps的返回結果
      const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps)

      if (hasRunOnce) {
        // pure為fales 或者mergedProps為null
        if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps))
          mergedProps = nextMergedProps
      } else {
        // 不是第一次執行,hasRunOnce標記為false
        hasRunOnce = true
        // nextMergedProps賦值給mergedProps
        mergedProps = nextMergedProps

        if (process.env.NODE_ENV !== 'production')
          verifyPlainObject(mergedProps, displayName, 'mergeProps')
      }

     // 返回修改後的props
      return mergedProps
    }
  }
}

export function whenMergePropsIsFunction(mergeProps) {
  // 如果mergeProps為true且是function, 呼叫wrapMergePropsFunc返回function initMergePropsProxy(){}
  return typeof mergeProps === 'function'
    ? wrapMergePropsFunc(mergeProps)
    : undefined
}

export function whenMergePropsIsOmitted(mergeProps) {
  // mergeProps !== true retun () => { ...ownProps, ...stateProps, ...dispatchProps }
  return !mergeProps ? () => defaultMergeProps : undefined
}

// 後向前執行
export default [whenMergePropsIsFunction, whenMergePropsIsOmitted]

/**
 * [mergeProps(stateProps, dispatchProps, ownProps): props] (Function):
 *  如果指定了這個引數,mapStateToProps() 與 mapDispatchToProps() 的執行結果和元件自身的 props 將傳入到這個回撥函式中。
 *  該回撥函式返回的物件將作為 props 傳遞到被包裝的元件中
 */
複製程式碼

相關文章