react-redux原理解析(第二彈)

本文原文連接html

在以前的一篇文章中已講過redux 原理解析,我將redux返回的store對象掛載在window中,不是太懂的同窗能夠看看以前的redux 原理解析react

const reducer = combineReducers({
    home: homeNumber,
    number: addNumber
})
const store = createStore(reducer)
window.$reduxStore = store

// 使用
window.$reduxStore.dispatch(action);
let { state } = window.$reduxStore.getState()
複製代碼

但在平時的開發中,通常是將redux+react-redux+react配合使用,那麼,下面就一塊兒來看看react-redux中的常見方法,它具體作了什麼。【下面是以最新版本react-redux@7.1.3庫解析】(下面的源碼部分省略)git

#Provider函數

react-redux提供了<Provider />組件用來掛載redux返回的store對象,同時將整個應用做爲Provider的子組件。github

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
)
複製代碼

下面看看<Provider />組件作了什麼:redux

function Provider({ store, context, children }) {
  // 依賴項store變化觸發,返回store和subscription
  const contextValue = useMemo(() => {
    // 訂閱監聽Subscription函數,下面會專門說到
    const subscription = new Subscription(store)
    subscription.onStateChange = subscription.notifyNestedSubs
    return {
      store,
      subscription
    }
  }, [store])
  //...
  
  // ReactReduxContext = React.createContext(null)
  const Context = context || ReactReduxContext

  return <Context.Provider value={contextValue}>{children}</Context.Provider>
}
// 源碼地址:https://github.com/reduxjs/react-redux/blob/master/src/components/Provider.js#L6
複製代碼

Provider接收三個參數,store參數接收store對象,context參數接收上下文對象,children參數接收ReactElement元素; 在原應用組件上包裹一層,使原來整個應用成爲Provider的子組件,Context.Provider API只有當 Provider 的 value 值發生變化時,它內部的全部消費組件都會從新渲染數組

contextValue掛載在Provider上,contextValue包含store對象和訂閱發佈subscription對象,以備connect組件使用獲取。Subscription主要負責connect、Provider組件的更新,屬於核心內容,這些將在下面講到。bash

#connect函數

connect的常見使用示例:微信

return connect(mapStateToProps, mapDispatchToProps)(Component)
複製代碼

connect函數就是一個高階組件,將以上的參數傳入函數,返回一個新的組件,主要就是將statedispatch等屬性掛載Component的props上。app

import hoistStatics from 'hoist-non-react-statics'
import { ReactReduxContext } from './Context';

// 核心函數 return組件
function ConnectFunction (props) {
  //  ...
  // 判斷props 是否存在store & getState & dispatch,通常都爲false
  var didStoreComeFromProps = Boolean(props.store) && Boolean(props.store.getState) && Boolean(props.store.dispatch);
  // 獲取Provider組件掛載的contextValue
  var ContextToUse = useMemo(function () {
    return propsContext &&
          propsContext.Consumer &&
          isContextConsumer(<propsContext.Consumer />)
          ? propsContext
          : Context
  })
  // contextValue = { store, subscription }
  var contextValue = useContext(ContextToUse)

  //  ...
  // 依賴返回 contextValue
  var overriddenContextValue = useMemo(function () {
    if (didStoreComeFromProps) {
      return contextValue
    }
    return {
      ...contextValue,
      subscription
    }
  }, [didStoreComeFromProps, contextValue, subscription]);

  //  ...
  // actualChildProps == props,上掛載了```state```和```dispatch```等屬性
  const renderedWrappedComponent = useMemo(
    () => <WrappedComponent {...actualChildProps} ref={forwardedRef} />,
    [forwardedRef, WrappedComponent, actualChildProps]
  )
  // 返回渲染ReactElement
  var renderedChild = useMemo(function () {
    // 判斷是否存在mapStateToProps函數
    if (shouldHandleStateChanges) {
      return (
        <ContextToUse.Provider value={overriddenContextValue}>
          {renderedWrappedComponent}
        </ContextToUse.Provider>
      )
    }
    // renderedWrappedComponent 渲染容器組件
    return renderedWrappedComponent
  }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]);
  return renderedChild;
}
//  ...
// 與Object.assign相似操做
return hoistStatics(ConnectFunction, WrappedComponent)
複製代碼

hoistStaticside

函數的做用就是相似於Object.assign,能夠這樣理解 hoistStatics(targetComponent, sourceComponent)hoist-non-react-statics庫。上面代碼中 ConnectFunction是核心函數,雖然中間部分代碼省略,不過留下的都是精華。

ConnectFunction函數中,經過hooks useContext獲取Provider組件的contextValue對象;renderedWrappedComponentactualChildProps

做爲props傳入,actualChildProps是已經處理過的props屬性,上面已經掛載了 dispatch函數和 state狀態等屬性;而 renderedChild組件,其實connect函數最後返回的實際內容。(中間部分代碼省略了 源碼連接跳轉)

以上就是Provice組件和connect組件初次調用時,所通過的實際代碼,固然在其中有一些刪減,不過基本都有說到。

當dispatch被調用時,會發生什麼呢?上面部分徹底沒有說到,下面咱們就來看看當dispatch(action)調用後,react-redux內部,是如何更新connect組件。

#connect如何更新?

當在React應用中調用dispatch函數時,redux中實際調用的就是reducer函數,同時返回新的state。那麼redux-react中的connect組件如何更新呢,下面咱們來一塊兒看看更新的流程:

dispatch(action)
複製代碼

下面內容不是精讀代碼,只是聊一下基本的流程

#初始化

Provider組件被調用註冊時,訂閱更新Subscription函數被註冊:

const subscription = new Subscription(store)
複製代碼

在redux-react中,訂閱發佈函數Subscription

是其中的核心函數(訂閱發佈模式是其核心),它有效的保證connect組件的更新渲染。store做爲參數傳入到Subscription構造函數內部,做用就是Subscription內部使用

#訂閱

return connect(mapStateToProps, mapDispatchToProps)(Component)
複製代碼
// react-redux中,checkForUpdates函數,負責更新connect組件
subscription.onStateChange = checkForUpdates

// redux保存觸發 通知函數
store.subscribe(subscription.notifyNestedSubs);

// react-redux保存 更新函數
subscription.listeners.subscribe(subscription.onStateChange)
複製代碼

在connect組件被使用時,react-redux中的subscription對象,會將connect組件的checkForUpdates更新函數做爲參數,傳入保存在subscription的訂閱數組next中;同時,redux也會發生相同的操做【在react-redux中也有使用到redux中方法】。(代碼爲簡化版)

// redux中 subscribe函數
let nextListeners = []
subscribe(listener) {
   // ... 
   nextListeners.push(listener)
}
// react-redux中 subscribe函數
let next = []
subscribe(listener) {
   // ... 
   next.push(listener)
}

複製代碼

checkForUpdates

函數負責connect組件的內部的狀態更新。

notifyNestedSubs函數是做用是觸發react-redux中的subscription對象的更新函數,而它被保存在nextListeners數組中,是爲了當redux的dispatch函數被觸發時,調用notifyNestedSubs通知函數,進而觸發react-redux的connect組件的checkForUpdates更新函數。

react-redux:checkForUpdates 函數源碼

react-redux:Subscription 函數源碼

#發佈(更新)

dispatch(action)發起觸發操做後,固然是觸發store中dispatch函數了,修改store中state的值,更新遍歷redux的nextListeners訂閱數組,觸發通知函數notifyNestedSubs調用;同時,這會致使react-redux中的next數組,被觸發遍歷調用。兩個庫基本都是下面的代碼

let next = listeners;​
for (let i = 0; i < listeners.length; i++) {
  listeners[i]()
}
複製代碼

以上細節可能有所省略,但基本就是先訂閱,將更新函數保存進入訂閱數組,而後當dispatch函數被調用時,遍歷調用訂閱數組,完成connect組件的更新。

在最新的react-redxu庫中 ,有大量的React Hooks出現,中間我木有過多的說明,有興趣能夠自行研究。(高階組件、React Hooks、訂閱發佈模式)

ps: 微信公衆號:Yopai,有興趣的能夠關注,每週不按期更新。不斷分享,不斷進步

相關文章
相關標籤/搜索