redux 和 react-redux 部分源碼閱讀

從源代碼的入口文件發現,其實 redux 最終就只是導出了一個對象,對象中有幾個方法,代碼以下:html

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}
複製代碼

因此重點分析幾個方法:react

createStore 方法

方法中定義的一些變量:git

let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
複製代碼

這些變量會被 dispatch 或者別的方法引用,從而造成閉包。這些變量不會被釋放。github

建立 srore 的方法最終返回的是一個對象。對象中含有比較重要的方法dispatch,subscribe,getState編程

return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
}
複製代碼

其中 createStore 的第三個參數是應用中間件來作一些加強操做的。redux

if (typeof enhancer !== 'undefined') { // 若是加強方法存在就對 createStore 進行加強
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
}
複製代碼

subscribe 方法

其中 subscribe 用來註冊監聽方法,每一次註冊後會將監聽方法維護到數組currentListeners中,currentListenerscreateStore 中的一個變量,因爲被 subscribe 引用着因此造成了一個閉包。也就是經過閉包來維護狀態。數組

let currentListeners = []
複製代碼

dispatch 方法

dispatch 方法用來分發 action, 函數裏面會生成新的 currentState, 會執行全部註冊了的函數。閉包

核心代碼:app

try {
  isDispatching = true
  currentState = currentReducer(currentState, action) // 生成新的 state
} finally {
  isDispatching = false
}

const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
  const listener = listeners[i]
  listener()
} // 遍歷執行註冊函數
複製代碼

getState

僅僅用來得到當前的 state:異步

function getState() {
    return currentState
}
複製代碼

combineReducers 函數

函數中定義的一些變量,

const finalReducers = {}
const finalReducerKeys = Object.keys(finalReducers)
複製代碼

這個函數最後返回的是一個函數 combination, 返回的函數中引用了 finalReducersfinalReducerKeys,造成了閉包。

出於業務場景考慮,不一樣的模塊採用不一樣的 reducer 進行處理,因此 reducer 函數有不少。這些 reducer 會遍歷執行。

每一次 dispatch 一個 action 的時候就會執行

currentState = currentReducer(currentState, action) // 生成新的 state
複製代碼

這裏的 currentReducer 就是返回的 combination 函數。combination 函數中的核心代碼:

function combination(state = {}, action) {
    ...
    let hasChanged = false
    // 每一次 reducer 執行的時候都會生成一個新的對象來做爲新的 state 
    const nextState = {}
    // 經過 for 循環遍歷 reducer 
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      
      // 獲取當前的 state
      const previousStateForKey = state[key]
      
      // 執行相應的 reducer 後會生成新的 state
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      
      // 給新的 state 賦值
      nextState[key] = nextStateForKey
      
      // 若是是一個簡單類型好比 string,number 
      // 若是先後值同樣就不會觸發改變
      // 但若是 state 中某個值是一個對象,
      // 儘管先後對象中的值同樣,可是引用地址變化,仍是會觸發改變
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    
    // 因此若是簡單值沒有變化而且沒有對象的引用地址改變就會返回原來的 state
    return hasChanged ? nextState : state
}
複製代碼

結合 react-redux 中向 redux 訂閱的方法發現

subscribe() {
    const { store } = this.props  // 這裏的 store 是 createStore 方法執行後返回的對象
     
    this.unsubscribe = store.subscribe(() => { // 經過訂閱方法註冊監聽事件
      const newStoreState = store.getState() // 獲取新的 state
    
      if (!this._isMounted) {
        return
      }
    
      // 經過使用函數替代對象傳入 setState 的方式可以獲得組件的 state 和 props 屬性可靠的值。
      this.setState(providerState => {
        // 若是值是同樣的就不會觸發更新
        if (providerState.storeState === newStoreState) {
          return null
        }
    
        return { storeState: newStoreState }
      })
    })
    
    // Actions might have been dispatched between render and mount - handle those
    const postMountStoreState = store.getState()
    if (postMountStoreState !== this.state.storeState) {
      this.setState({ storeState: postMountStoreState })
    }
}
複製代碼

在註冊的 listen 方法中會發現若是最 新的state和原來的state同樣 就不會觸發 setState 方法的執行,從而就不會觸發 render

applyMiddleware 使用中間件

源碼:

export default function applyMiddleware(...middlewares) {
    return createStore => (...args) => { // 接收 createStore 函數做爲參數
        const store = createStore(...args)
        let dispatch = () => {
          throw new Error(
            `Dispatching while constructing your middleware is not allowed. ` +
              `Other middleware would not be applied to this dispatch.`
          )
        }
        
        const middlewareAPI = {
          getState: store.getState,
          dispatch: (...args) => dispatch(...args)
        } // 中間件函數接收的 API 參數,可以獲取到當前的 state 和 createStore 函數的參數
        // 因此這裏就向中間件函數中傳遞了參數
        const chain = middlewares.map(middleware => middleware(middlewareAPI))
        // 經過函數組合生成一個新的 dispatch 函數
        dispatch = compose(...chain)(store.dispatch)
        
        return {
          ...store,
          dispatch
        } // 這裏返回的是最後生成的 store,相比不使用中間件的區別是對 dispatch 進行了加強。
    }
}
複製代碼

結合 createStore 中的源碼:

return enhancer(createStore)(reducer, preloadedState)
複製代碼

因此上面 applyMiddleware 中返回的函數就是這裏的 enhancer 方法,接收 createStore 做爲參數。

(reducer, preloadedState) 對應着中間件中的 (...args)

react-redux

react-redux 經過提供 Provider 組件將 store 和整個應用中的組件聯繫起來。確保整個組件均可以得到 store, 這是經過 Context 來實現的。

Provider 組件最終渲染的組件:

render() {
    const Context = this.props.context || ReactReduxContext

    return (
      <Context.Provider value={this.state}>
        {this.props.children}
      </Context.Provider>
    )
}
複製代碼

其中 state 的定義以下:

const { store } = props
this.state = {
  storeState: store.getState(),
  store
}
複製代碼

因此 Provider 給應用提供 store 的寫法以下,屬性名必須是 store

<Provider store={store}>
  <Router />
</Provider>
複製代碼

redux-thunk

redux-thunk 是一箇中間件,直接看中間件的源代碼是絕對不可能看明白的

中間件不是一個完整的個體。它是爲了豐富或者擴展某個模塊而出現的,其中會調用一些原來的模塊的方法,因此若是不看源模塊的對應的方法實現,根本沒法理解。

因此要想看懂一箇中間件,必須結合源模塊的代碼一塊兒看。

Thunk 函數的含義和用法

JavaScript 語言是傳值調用,它的 Thunk 函數含義有所不一樣。在 JavaScript 語言中,Thunk 函數替換的不是表達式,而是多參數函數,將其替換成單參數的版本,且只接受回調函數做爲參數

如何讓 dispatch 分發一個函數,也就是 action creator??

dispatch 的參數只能是一個普通的對象,若是要讓參數是一個函數,須要使用中間件 redux-thunk

設計思想就是一種面向切面編程AOP,對函數行爲的加強,也是裝飾模式的使用

redux-thunk 源碼:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;
複製代碼

若是隻是用了 thunk,那麼最終加強版的 dispatch 就是

action => {
    // 當 dispatch 參數是一個函數的時候執行這裏
    if (typeof action === 'function') { 
      // 這裏的 dispatch 就是最原始的 dispatch
      // 因此 action 函數中能夠直接使用參數 dispatch 和 getState 函數
      return action(dispatch, getState, extraArgument);
    }

    return next(action); // 這裏的 next 是 store.dispatch
}
複製代碼

異步操做帶代碼

異步操做若是使用 action creator, 則至少要送出兩個 Action:

  • 用戶觸發第一個 Action,這個跟同步操做同樣,沒有問題;
  • action creator 函數中送出第二個 Action

代碼實例:

handleClick = () => {
    const { dispatch } = this.props
    dispatch(this.action); // 發出第一個 action(函數)
}

action = (dispatch, getState) => setTimeout(() => {
    dispatch({ type: 'REQUESTSTART' })
}, 1000) // 發出第二個 action(普通對象)
複製代碼

思考

異步代碼的處理必定要使用 redux-thunk嗎?

非也。在觸發含有異步代碼的函數執行時,把 dispatch 函數做爲一個參數傳給函數,而後這個異步函數裏面在合適的時機調用 dispatch 發出 action 就行。

上面的異步代碼可改寫以下:

handleClick = () => {
    const { dispatch } = this.props
    this.action(dispatch);
}

action = dispatch => setTimeout(() => {
    dispatch({ type: 'REQUESTSTART' })
}, 1000)
複製代碼

不過相比 redux-thunk 有個缺陷就是不能獲取 getState 這個方法。

使用示例

使用 redux 演示代碼

相關文章
相關標籤/搜索