React-redux源碼分析

React-redux源碼總共分爲兩部分,第一是Provider,第二是connectreact

Provider

Provider比較簡單,主要代碼以下:git

class Provider extends Component {
        getChildContext() {
          return { [storeKey]: this[storeKey], [subscriptionKey]: null }
        }

        constructor(props, context) {
          super(props, context)
          this[storeKey] = props.store;
        }

        render() {
          return Children.only(this.props.children)
        }
    }
複製代碼

即經過context api將store傳到子組件裏面去。github

connect

connect算是一個比較有難度的部分,函數原型是:redux

connect(mapStateToProps,mapDispatchToProps,[mergeProps]) => (WrappedComponent) => HOC
複製代碼

咱們嘗試對其進行由上到下的分析:api

入口

函數入口就是一個三階函數:數組

export function createConnect({ connectHOC, mapStateToPropsFactories, mapDispatchTpropsFactories, mergePropsFactories, selectFactory }) => connect(mapStateToProps,mapDispatchToProps,mergeProps, {/*...*/}) => {

    const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps')
    const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')
    const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
    
    return connectHOC(selectorFactory, {
        initMapStateToProps,
        initMapDispatchToProps,
        initMergeProps,
        //...
    })
}
export default createConnect()
複製代碼

寫的其實比較繞,可是理解起來不難,首先createConnect方法只至關於一個包裝而已,將高階組件connectHOC,以及一些工廠函數引入到connect的父scope中來。markdown

而後在connect中,經過match函數來選擇適當的factory來包裝外界傳入的參數)生成三個函數: initMapStateToProps,initMapDispatchToPropsinitMergeProps閉包

match函數的源碼:app

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}.`)
  }
}
複製代碼

這段代碼其中arg就是connect函數接收的mapStateToProps等,以倒序的方式尋找第一個返回非空的factory。咱們能夠先暫且不去深刻推敲。ide

ConnectHOC

connectHOC是整個react-redux的核心,在分析源代碼以前,咱們先看看一個工具函數:

function makeSelectorStateful(sourceSelector, store) {
  const selector = {
    run: function runComponentSelector(props) {
      try {
        const nextProps = sourceSelector(store.getState(), props)
        if (nextProps !== selector.props || selector.error) {
          selector.shouldComponentUpdate = true
          selector.props = nextProps
          selector.error = null
        }
      } catch (error) {
        selector.shouldComponentUpdate = true
        selector.error = error
      }
    }
  }

  return selector
}
複製代碼

selector主要做用是對redux的state和dispatch與react的props進行合併(mergeProps) 這段代碼主要是對sourceSelector進行封裝,增長了錯誤處理,新舊值的比較等。

下面是connectHOC的主要源碼:

// ...
export default function connectAdvanced( selectorFactory, // options object: { //省略掉的參數: withRef,displayName,renderCountProp,shouldHandleStateChanges,storeKey,getDisplayName // 傳入selectorFactory中額外的參數 // 主要包括 `initMapStateToProps`,`initMapDispatchToProps``initMergeProps` ...connectOptions } = {} ) {
  const subscriptionKey = storeKey + 'Subscription'
  // ...

  return function wrapWithConnect(WrappedComponent) {
    // ... 
    const selectorFactoryOptions = {
      ...connectOptions,
      //... 
      WrappedComponent
    }
    
    // 聲明一個React組件
    class Connect extends Component {
      constructor(props, context) {
        super(props, context)
        // ...
        // 從props或者context裏面得到redux store
        this.store = props[storeKey] || context[storeKey]
        this.propsMode = Boolean(props[storeKey])
        this.initSelector()
        this.initSubscription()
      }
      
      initSelector() {
        // 結合mapPropsTo...和mergeProps函數處理參數。(state, props) => nextProps
        const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
        // makeSelectorStateful: 包裝sourceSelector函數,比較props和nextProps肯定shouldComponentUpdate
        this.selector = makeSelectorStateful(sourceSelector, this.store)
        // 更新當前參數,實際上就是ownProps
        this.selector.run(this.props)
      }

      initSubscription() {
        // 若是不須要相應redux state變化,則不須要訂閱
        if (!shouldHandleStateChanges) return
        // 獲取祖先傳下來的訂閱對象
        const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
        
        // 生成當前組件的訂閱對象,傳入parentSub。
        // 當parentSub不爲空的時候,onStateChange其實是註冊到了parentSub的listeners數組,由組件手動調用notifyNestedSubs執行。
        // 不然直接使用store.subscribe註冊到redux中,由redux控制執行
        // 這樣作能始終保證祖先節點的更新早於子孫節點
        this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
        this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
      }

      onStateChange() {
        // 更新props
        this.selector.run(this.props)
        if (!this.selector.shouldComponentUpdate) {
          // 若是當前組件不須要更新,那麼直接更新子組件
          this.notifyNestedSubs()
        } else {
         // 先更新當前組件,再更新子組件
          this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
          // dummyState 爲空,意味觸發react的更新機制
          this.setState(dummyState)
        }
      }
      getChildContext() {
        // 若是store是從props中得到,那麼直接將祖先的subscription對象傳下去。(僅做爲一個傳話人,不響應父組件的dispatch事件)
        // 不然,將自身的subscription傳下去,形狀以下
        // Component A (B's subscription's parentSub == A)
        // |
        // -> Component B (C's subscription's parentSub == B)
        // |
        // -> Component C (E's subscription's parentSub == C)
        // |
        // |== Component D (propsMode === true) directly transfer C's sub context to E
        // |
        // -> Component E
        // when a dispatch occured in Component A,the callback exec order will be A -> B -> C -> E
        
        const subscription = this.propsMode ? null : this.subscription
        return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
      }

      componentDidMount() {
        if (!shouldHandleStateChanges) return
        // 由於componentWillMount在ssr中執行,而componentDidMount和componentWillUnmount不執行。所以若是將trySubscribe方法放在componentWillMount中的話,在ssr中可能會由於沒有調用componentWillUnmount的unsubscribe而內存泄漏
        this.subscription.trySubscribe()
        
        // run即update,更新selector.nextProps,selector.shouldComponentUpdate
        this.selector.run(this.props)
        
        // 若是子組件在componentWillMount的時候dispatch了一個action,於是沒有被上文的subscription捕獲到,此時的selector就會檢測出參數的不一致(shouldComponentUpdate = true),因此須要強制更新
        if (this.selector.shouldComponentUpdate) this.forceUpdate()
      }

      // 更新props
      componentWillReceiveProps(nextProps) {
        this.selector.run(nextProps)
      }

      shouldComponentUpdate() {
        return this.selector.shouldComponentUpdate
      }

      componentWillUnmount() {
        if (this.subscription) this.subscription.tryUnsubscribe()
        
        // 爲何直接使用subscription.notifyNestedSubs而要拷貝出來分別處理:
        // 防止在notify過程當中subscription爲null,而null.notifyNestedSubs報錯
        this.subscription = null
        this.notifyNestedSubs = noop
        
        this.store = null
        this.selector.run = noop
        this.selector.shouldComponentUpdate = false
      }


      notifyNestedSubsOnComponentDidUpdate() {
        // 避免屢次調用
        this.componentDidUpdate = undefined
        this.notifyNestedSubs()
      }

      isSubscribed() {
        return Boolean(this.subscription) && this.subscription.isSubscribed()
      }

      addExtraProps(props) {
        // 若是沒有額外的參數,直接返回props
        if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props
        // 防護性拷貝
        const withExtras = { ...props }
        if (withRef) withExtras.ref = this.setWrappedInstance
        if (renderCountProp) withExtras[renderCountProp] = this.renderCount++
        if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription
        return withExtras
      }

      render() {
        const selector = this.selector
        selector.shouldComponentUpdate = false

        if (selector.error) {
          throw selector.error
        } else {
          return createElement(WrappedComponent, this.addExtraProps(selector.props))
        }
      }
    }
    // ...
    
    // 僅在開發環境下支持的的熱重載
    if (process.env.NODE_ENV !== 'production') {
      Connect.prototype.componentWillUpdate = function componentWillUpdate() {
        // We are hot reloading!
        if (this.version !== version) {
          this.version = version
          this.initSelector()
          
          let oldListeners = [];

          if (this.subscription) {
            oldListeners = this.subscription.listeners.get()
            this.subscription.tryUnsubscribe()
          }
          this.initSubscription()
          if (shouldHandleStateChanges) {
            this.subscription.trySubscribe()
            oldListeners.forEach(listener => this.subscription.listeners.subscribe(listener))
          }
        }
      }
    }
   // 將WrappedComponent中的非React特定的靜態屬性(例如propTypes就是React的特定靜態屬性)賦值到Connect。
   // 做用有點相似於Object.assign,可是僅複製非React特定的靜態屬性。
    return hoistStatics(Connect, WrappedComponent)
  }
}
複製代碼

至此,大部分邏輯咱們已經很清楚了。如今咱們來分析一下各類factory所作的事情:

selectorFactory

首先看主體:

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
  )
}
複製代碼

該函數至關於一個控制器,根據pure選項來決定使用那種具體實現。 若是puretrue,則selectorFactory將會返回一個能夠記憶結果的selector,這樣纔有可能使得connectHOC裏面的selector.shouldComponentUpdatefalse。反之,selector將永遠返回新的對象,selector.shouldComponentUpdate始終爲true

impureFinalPropsSelectorFactory

export function impureFinalPropsSelectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch ) {
  return function impureFinalPropsSelector(state, ownProps) {
    return mergeProps(
      mapStateToProps(state, ownProps),
      mapDispatchToProps(dispatch, ownProps),
      ownProps
    )
  }
}
複製代碼

直接就很粗暴了

pureFinalPropsSelectorFactory

export function pureFinalPropsSelectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, { areStatesEqual, areOwnPropsEqual, areStatePropsEqual } ) {
// 閉包,memory以前的結果,僅當有變化的時候才更新
  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
    return mergedProps
  }

  function handleNewPropsAndNewState() {
    // state 已經 changed
    stateProps = mapStateToProps(state, ownProps)

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

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

  function handleNewProps() {
    // 僅當state 用到了OwnProps才更新
    if (mapStateToProps.dependsOnOwnProps)
      stateProps = mapStateToProps(state, ownProps)
    // 僅當disaptch 用到了OwnProps才更新
    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps)
      
    
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }

  function handleNewState() {
    const nextStateProps = mapStateToProps(state, ownProps)
    // shallow compare state是否改變
    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 = nextState
    ownProps = nextOwnProps
    
    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)
  }
}
複製代碼

看上去複雜許多,但主要仍是由於須要區分是不是首次調用而已。

mapStateToProps

mapStateToPropsmapDispatchToProps差很少,大部分都是公共代碼(wrapMapToProps),所以咱們以mapStateToProps爲例:

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

export function whenMapStateToPropsIsFunction(mapStateToProps) {
  return (typeof mapStateToProps === 'function')
    ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
    : undefined
}

export function whenMapStateToPropsIsMissing(mapStateToProps) {
  return (!mapStateToProps)
    ? wrapMapToPropsConstant(() => ({}))
    : undefined
}

export default [
  whenMapStateToPropsIsFunction,
  whenMapStateToPropsIsMissing
]
複製代碼

跟selectorFactory同樣,這裏一樣有多種解決方案,不過不一樣的是,這裏是直接將全部方案以數組的方式返回,供前面connect中的match函數匹配。

而後咱們主要來看一下其中涉及到的幾個函數:

wrapMapToPropsConstant

export function wrapMapToPropsConstant(getConstant) {
  return function initConstantSelector(dispatch, options) {
    const constant = getConstant(dispatch, options)
    function constantSelector() { return constant }
    constantSelector.dependsOnOwnProps = false 
    return constantSelector
  }
}
複製代碼

顧名思義,constant不容許結果發生變化的,因此函數裏面調用傳入的getConstant函數以後,直接返回一個getter函數:contantSelector

wrapMapToPropsFunc

// methodName只用來debug
export function wrapMapToPropsFunc(mapToProps, methodName) {
  // options 只須要 displayname , 也只用來debug
  return function initProxySelector(dispatch, { displayName }) {
    const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
      return proxy.dependsOnOwnProps
        ? proxy.mapToProps(stateOrDispatch, ownProps)
        : proxy.mapToProps(stateOrDispatch)
    }

    proxy.dependsOnOwnProps = true

    proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
      proxy.mapToProps = mapToProps
      proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
      let props = proxy(stateOrDispatch, ownProps)
      // 若是mapToProps執行後返回的結果仍是一個函數,則繼續遞歸調用,直到它扁平爲止
      if (typeof props === 'function') {
        proxy.mapToProps = props
        proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
        props = proxy(stateOrDispatch, ownProps)
      }

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

      return props
    }

    return proxy
  }
}
複製代碼

這裏主要運用了代理的方法,挺巧妙的對mapToProps方法進行了包裝,增長了dependsOnOwnProps這個靜態成員,判斷方法很簡單,直接判斷Function的參數長度便可:

getDependsOnOwnProps

export function getDependsOnOwnProps(mapToProps) {
  return (mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined)
    ? Boolean(mapToProps.dependsOnOwnProps)
    : mapToProps.length !== 1
}
複製代碼

至此,全部代碼都通了, cheers!~

參考

  1. react-redux源碼解析
  2. react-redux源碼分析
  3. 庖丁解牛React-Redux(一): connectAdvanced
相關文章
相關標籤/搜索