reduxソース読解

8689 ワード

reduxソース読解
まずreduxの公式例からreduxの役割を見る
 import { createStore } from 'redux'
  ​
  function counter(state = 0, action) {
    switch (action.type) {
      case 'INCREMENT':
        return state + 1
      case 'DECREMENT':
        return state - 1
      default:
        return state
    }
  }
  ​
  let store = createStore(counter)
  ​
  store.subscribe(() => console.log(store.getState()))
  ​
  store.dispatch({ type: 'INCREMENT' })
  // 1
  store.dispatch({ type: 'INCREMENT' })
  // 2
  store.dispatch({ type: 'DECREMENT' })
  // 1

このように簡単に見ると、reduxは登録購読+データ管理のように感じられ、storeにstateツリーが格納、reducerによってのみ変更可能であるが、reducerの2番目のパラメータはactionであり、ユーザーはdispatch特定actionによってreducerを呼び出してstateを変更する.
公式の例からcreateStoreの重要性がわかりますので、私たちの解読もcreateStoreから始まります.
1. CreateStore
enhancer(拡張機能に相当)の関連コードを一時的に削除し、storeの実行を理解しやすい
  import isPlainObject from 'lodash/isPlainObject'
  import $observable from 'symbol-observable'
  ​
  export const ActionTypes = {
    INIT: '@@redux/INIT'
  }
  ​
  export default function createStore(reducer, preloadedState) {
    if (typeof reducer !== 'function') {
      throw new Error('Expected the reducer to be a function.')
    }
  ​
    // reducer,          ,       action.type           state
    let currentReducer = reducer
    // state,            
    let currentState = preloadedState
    //         
    let currentListeners = []
    //           ,       next            /  
    let nextListeners = currentListeners
    //        
    let isDispatching = false
  ​
    //                    
    //                               
    //                               
    function ensureCanMutateNextListeners() {
      if (nextListeners === currentListeners) {
        nextListeners = currentListeners.slice()
      }
    }
  ​
    //      ,              
    //        ,           ?           action      state
    function getState() {
      return currentState
    }
  ​
    //        ,    listener                  
    //            
    function subscribe(listener) {
      if (typeof listener !== 'function') {
        throw new Error('Expected listener to be a function.')
      }
  ​
      let isSubscribed = true
  ​
      ensureCanMutateNextListeners()
      nextListeners.push(listener)
  ​
      return function unsubscribe() {
        if (!isSubscribed) {
          return
        }
  ​
        isSubscribed = false
  ​
        ensureCanMutateNextListeners()
        const index = nextListeners.indexOf(listener)
        nextListeners.splice(index, 1)
      }
    }
  ​
    //        ,                
    function dispatch(action) {
      if (!isPlainObject(action)) {
        throw new Error(
          'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
        )
      }
  ​
      if (typeof action.type === 'undefined') {
        throw new Error(
          'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
        )
      }
  ​
      if (isDispatching) {
        throw new Error('Reducers may not dispatch actions.')
      }
  ​
      try {
        isDispatching = true
        currentState = currentReducer(currentState, action)
      } finally {
        isDispatching = false
      }
  ​
      const listeners = currentListeners = nextListeners
      for (let i = 0; i < listeners.length; i++) {
        const listener = listeners[i]
        listener()
      }
  ​
      return action
    }
  ​
    //    reducer
    function replaceReducer(nextReducer) {
      if (typeof nextReducer !== 'function') {
        throw new Error('Expected the nextReducer to be a function.')
      }
  ​
      currentReducer = nextReducer
      dispatch({ type: ActionTypes.INIT })
    }
  ​
    //         ,     state      ,         observer   next   
    //      prop `[$observable]`   ,           ,
    function observable() {
      const outerSubscribe = subscribe
      return {
        subscribe(observer) {
          if (typeof observer !== 'object') {
            throw new TypeError('Expected the observer to be an object.')
          }
  ​
          function observeState() {
            if (observer.next) {
              observer.next(getState())
            }
          }
  ​
          observeState()
          const unsubscribe = outerSubscribe(observeState)
          return { unsubscribe }
        },
  ​
        [$observable]() {
          return this
        }
      }
    }
  ​
    dispatch({ type: ActionTypes.INIT })
  ​
    return {
      dispatch,
      subscribe,
      getState,
      replaceReducer,
      [$observable]: observable
    }
  }

コードにはいくつかのポイントがあります.
1.1 currentListenersとNextListenersが存在する理由
NextListenerが存在しない場合、サブスクライバに通知する途中で他のサブスクライバやサブスクライバが発生し、エラーや不確実性を招く
  const store = createStore(reducers.todos)
  const unsubscribe1 = store.subscribe(() => {
      //    listener             
      const unsubscribe2 = store.subscribe(()=>{})
      unsubscribe2()
  })

上記のコードのように、dispatchによってstoreが変更され、呼び出しサブスクライバ(listeners)が遍歴されると、1つのlistenerがリスナーキューを変更するため、NextListenersのコピーがなければ、遍歴中の配列にcurrentListenersという直接変更され、遍歴中のindexが信頼できなくなる
これは唯一の方法ではないかもしれません.reduxのissueを調べたところ、お兄さんがずっと前にnextListenersを取り除く方法を提出していたことがわかりました.https://github.com/reduxjs/redux/pull/2376しかし、結局は予測不可能で受け入れられなかったようだ.
そこでNextListenersに参加する必要があり、今回の通知で購読がキャンセルされたかどうかにかかわらず、次の購読にのみ影響し、現在の結果には影響しません.
参考ブログ:Redux:自問自答ですが、例としては自分で書いた方が分かりやすいと思います(x)
2. applyMiddleware
前述したenhancerは、applyMiddlewareがcreateStoreに転送可能なenhancerを返すことを示しています.
  export default function applyMiddleware(...middlewares) {
    return (createStore) => (reducer, preloadedState, enhancer) => {
      const store = createStore(reducer, preloadedState, enhancer)
      let dispatch = store.dispatch
      let chain = []

      //           
      const middlewareAPI = {
        getState: store.getState,
        dispatch: (action) => dispatch(action)
      }
      //              
      chain = middlewares.map(middleware => middleware(middlewareAPI))
      // compose              ,          (  )         
      //   c    chain         c3(c2(c1(dispatch)))
      // redux   compose    reduce    
      dispatch = compose(...chain)(store.dispatch)
  ​
      //           dispatch      
      return {
        ...store,
        dispatch
      }
    }
  }

このapplyMiddlewareは実は3級のコリー化(簡単な理解では、3回のパラメータを収集してから実行される)関数で、1回目は中間部品で、2回目はcreateStoreで、3回目はcreateStoreのパラメータです.composeは関数を組み合わせて、使う地方はとても多くて、関数式のプログラミングの思想を体現して、これは余分に見ることができて、言うまでもありません
3. bindActionCreators
このモジュールのソースコードは実際には以下の関数を見るだけでよいが,残りの部分は伝達されたactionCreatorsがオブジェクトであるか関数であるかによってそれぞれこの関数を呼び出すだけである.
  function bindActionCreator(actionCreator, dispatch) {
    return (...args) => dispatch(actionCreator(...args))
  }

ここでactionCreatorは、同じ処理が必要な一連のactionを生成するために使用できるaction:{type:'xxx'}を返します.
例えば入力したパラメータ...argsをpayloadとして対応するactionを生成する
  • combineReducers

  • この役割は名前のようにreducersを組み合わせることができ、大規模なプロジェクトでは各モジュールが自身のreducerを管理するのに便利である.
    このソースコードは比較的に長いので、私たちはまずいくつかの重要ではないエラーを削除して、彼の実装を確認します.
      export default function combineReducers(reducers) {
        const reducerKeys = Object.keys(reducers)
        const finalReducers = {}
        //      reducers     function   reducer
        for (let i = 0; i < reducerKeys.length; i++) {
          const key = reducerKeys[i]
      ​
          if (typeof reducers[key] === 'function') {
            finalReducers[key] = reducers[key]
          }
        }
    
        const finalReducerKeys = Object.keys(finalReducers)
      ​
        return function combination(state = {}, action) {
          let hasChanged = false
          const nextState = {}
          //    reducers   ,       reducer     state
        //      action       state     ,        change
          for (let i = 0; i < finalReducerKeys.length; i++) {
            const key = finalReducerKeys[i]
            const reducer = finalReducers[key]
            const previousStateForKey = state[key]
            const nextStateForKey = reducer(previousStateForKey, action)
            nextState[key] = nextStateForKey
            hasChanged = hasChanged || nextStateForKey !== previousStateForKey
          }
          return hasChanged ? nextState : state
        }
      }
    

    小結
    今回reduxのソースコードを読むと、閉パッケージに対する理解がまだ深くないと感じました.コリー化や戻り関数は私を気絶させました==実際のプロジェクトではこのような書き方は使わないはずです(可読性があまり高くないので)、しかし、これも私が他のソースコードを読むレベルを高めました.