Redux源碼(六) —— createStore.js

Source Time

import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'

export default function createStore(reducer, preloadedState, enhancer) {
  // 第一個參數為reducer达皿,函數類型
  // 第二個參數應為初始state姐刁,類型任意
  // 第三個參數為enhancer芥牌,函數類型,即applyMiddleware返回的函數
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    // 若preloadedState和enhancer均為函數聂使,
    // 或者參數個數多余三個且最后兩個都是函數類型壁拉,
    // 猜測使用了多個enhancer,拋出錯誤
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function'
    )
  }

  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    // 若preloadedState為函數類型柏靶,且enhancer為空弃理,
    // 猜測無初始state,第二個參數直接為enhancer
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      // 若enhancer存在但不是函數類型屎蜓,則拋出錯誤痘昌,提示enhancer應該是一個函數
      throw new Error('Expected the enhancer to be a function.')
    }

    // 若enhancer存在并且是函數類型,調用enhancer對遞歸入參createStore
    // 這里可以結合applyMiddleware理解,enhancer即為applyMiddleware的返回結果
    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    // 判斷reducer類型辆苔,非函數類型就報錯
    throw new Error('Expected the reducer to be a function.')
  }

  // 當前reducer
  let currentReducer = reducer
  // 當前state
  let currentState = preloadedState
  // 當前事件監(jiān)聽數組
  let currentListeners = []
  // 下一輪事件監(jiān)聽數組算灸,此處與ensureCanMutateNextListeners相關聯
  let nextListeners = currentListeners
  // 判斷是否正在dispatch的標志
  let isDispatching = false

  function ensureCanMutateNextListeners() {/* ... */}
  function getState() {/* ... */}
  function subscribe(listener) {/* ... */}
  function dispatch(action) {/* ... */}
  function replaceReducer(nextReducer) {/*  */}
  function observable() {/* ... */}

  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}

Analysis

由于createStore內部比較大,所以這里我將一些內部定義的函數拎出單獨描述作用驻啤,對于其他的部分可參考中文注釋內容菲驴。最后的英文注釋也很好的描述了在創(chuàng)建store之后通過dispatch一次名字叫作INIT的action來進行整個store的內部state初始化〗钟樱總結一下非函數部分內部的功能就是以下幾點內容:

  1. 判斷入參是否符合要求谢翎,即只能最多一個enhancer
  2. 兼容第二個參數為enhancer而沒有初始state的情況
  3. 處理有enhancer的創(chuàng)建邏輯
  4. 判斷reducer的正確性
  5. 定義內部功能變量
  6. 定義內部功能函數或提供給用戶的API
  7. 初始化store,初始化state
  8. 導出API

講完了非函數部分內容沐旨,接下來一個一個分析一下在createStore中定義的函數森逮。

ensureCanMutateNextListeners

function ensureCanMutateNextListeners() {
  if (nextListeners === currentListeners) {
    nextListeners = currentListeners.slice()
  }
}

由于nextListeners變量定義的時候是根據currentListeners獲得的數組引用(參考定義部分),所以為了不影響當前事件監(jiān)聽數組磁携,函數ensureCanMutateNextListeners會在需要更改nextListeners的時候先判斷一下是否是當前事件監(jiān)聽數組的引用褒侧,若是,則會用slice方法獲得一個同樣元素的不同數組作為新的nextListeners谊迄。

getState

function getState() {
  // 獲取當前store中state闷供,如果正在dispatch則報錯
  if (isDispatching) {
    throw new Error(
      'You may not call store.getState() while the reducer is executing. ' +
        'The reducer has already received the state as an argument. ' +
        'Pass it down from the top reducer instead of reading it from the store.'
    )
  }

  return currentState
}

函數getState主要用于返回當前store中的state對象,需要注意的是如果當前store正在進行dispatch操作统诺,那么就不能獲取state歪脏,而是拋出一個錯誤提示。

subscribe

function subscribe(listener) {
  if (typeof listener !== 'function') {
    throw new Error('Expected the listener to be a function.')
  }

  if (isDispatching) {
    throw new Error(
      'You may not call store.subscribe() while the reducer is executing. ' +
        'If you would like to be notified after the store has been updated, subscribe from a ' +
        'component and invoke store.getState() in the callback to access the latest state. ' +
        'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
    )
  }

  let isSubscribed = true

  ensureCanMutateNextListeners()
  nextListeners.push(listener)

  return function unsubscribe() {
    if (!isSubscribed) {
      return
    }

    if (isDispatching) {
      throw new Error(
        'You may not unsubscribe from a store listener while the reducer is executing. ' +
          'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
      )
    }

    isSubscribed = false

    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
  }
}

這里的subscribe函數主要用于添加用戶事件的監(jiān)聽粮呢,會在dispatch更新state后進行調用(詳情在dispatch部分說明)婿失,即將監(jiān)聽事件加入到nextListeners。需要注意的是啄寡,該函數返回的是一個用于解除監(jiān)聽的unsubscribe方法豪硅,這里利用的是閉包的經典用法,可以參考學習一下挺物。

dispatch

function dispatch(action) {
  if (!isPlainObject(action)) {
    // 判斷是否是符合要求的plain object
    throw new Error(
      'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
    )
  }

  if (typeof action.type === 'undefined') {
    // 判斷是否包含所需的type屬性
    throw new Error(
      'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
    )
  }

  if (isDispatching) {
    // 利用isDispatching判斷當前是否正在進行dipatch操作
    throw new Error('Reducers may not dispatch actions.')
  }
  // 更新標示懒浮,并利用當前reducer更新state
  try {
    isDispatching = true
    currentState = currentReducer(currentState, action)
  } finally {
    isDispatching = false
  }

  // 通過nextListeners獲得最新的當前事件監(jiān)聽數組
  const listeners = (currentListeners = nextListeners)
  for (let i = 0; i < listeners.length; i++) {
    // 遍歷觸發(fā)監(jiān)聽事件
    const listener = listeners[i]
    listener()
  }

  // 返回入參action
  return action
}

dispatch函數可以說是用戶接觸最多的一個api了,功能非常強大识藤,但是它的實現卻是非常好理解的砚著。

  1. 首先是對接受的入參——action進行類型校驗,判斷是否是合法的plain object痴昧;
  2. 其次是判斷這個action是否包含標示更新類型的type屬性赖草;
  3. 利用isDispatching判斷是否正在進行dispatch操作,如果是則拋出錯誤剪个;
  4. 更新isDispatching標志,并利用將action作為入參傳入currentReducer得到最新的當前state;
  5. 通過nextListeners獲取最新的事件監(jiān)聽數組扣囊,同時遍歷觸發(fā)每個監(jiān)聽事件乎折;
  6. 返回入參action備用。

replaceReducer

function replaceReducer(nextReducer) {
  if (typeof nextReducer !== 'function') {
    // 判斷nextReducer是否是符合要求的函數類型
    throw new Error('Expected the nextReducer to be a function.')
  }
  // 更新當前reducer為最新的reducer
  currentReducer = nextReducer
  // 觸發(fā)一次REPLACE類型的action用于使用最新的reducer更新當前store中state數據
  dispatch({ type: ActionTypes.REPLACE })
}

replaceReducer函數接受一個新的reducer函數——nextReducer作為入參侵歇,在內部替換掉currentReducer骂澄,同時主動dispatch一次REPLACE類型的私有action,用于應用最新的reducer方法更新state惕虑。從我的項目經歷來說坟冲,這個replaceReducer方法以及接下來的observable方法,使用頻率都不是太高溃蔫,不知道具體使用場景會是什么樣子以及其他人的情況如何健提。

observable

function observable() {
  // 根據subscribe方法定義outerSubscribe方法,備用
  const outerSubscribe = subscribe
  // 返回一個包含subscribe方法的對象
  return {
    /**
     * The minimal observable subscription method.
     * @param {Object} observer Any object that can be used as an observer.
     * The observer object should have a `next` method.
     * @returns {subscription} An object with an `unsubscribe` method that can
     * be used to unsubscribe the observable from the store, and prevent further
     * emission of values from the observable.
     */
    subscribe(observer) {
      // 接受一個對象作為觀察者observer
      if (typeof observer !== 'object' || observer === null) {
        // 校驗observer類型
        throw new TypeError('Expected the observer to be an object.')
      }
      // 定義一個監(jiān)聽state的方法
      function observeState() {
        if (observer.next) {
          // 運行observer對象的next方法伟叛,以當前store的state作為入參
          observer.next(getState())
        }
      }
      // 執(zhí)行一次observerState方法
      observeState()
      // 定義解除監(jiān)聽的方法私痹,并作為一個對象的屬性,返回該對象
      const unsubscribe = outerSubscribe(observeState)
      return { unsubscribe }
    },
    // 獲取當前對象的this指向
    [$$observable]() {
      return this
    }
  }
}

坦白說统刮,之前從來沒有接觸紊遵、使用過這個api,所以對于其作用知之甚少侥蒙,暫時只能通過代碼層面來解讀其作用暗膜,后面可以進一步了解一下。

All

index
compose
applyMiddleware
bindActionCreators
combineReducers
createStore

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末鞭衩,一起剝皮案震驚了整個濱河市学搜,隨后出現的幾起案子,更是在濱河造成了極大的恐慌醋旦,老刑警劉巖恒水,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異饲齐,居然都是意外死亡钉凌,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門捂人,熙熙樓的掌柜王于貴愁眉苦臉地迎上來御雕,“玉大人,你說我怎么就攤上這事滥搭∷岣伲” “怎么了?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵瑟匆,是天一觀的道長闽坡。 經常有香客問我,道長,這世上最難降的妖魔是什么疾嗅? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任外厂,我火速辦了婚禮,結果婚禮上代承,老公的妹妹穿的比我還像新娘汁蝶。我一直安慰自己,他們只是感情好论悴,可當我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布掖棉。 她就那樣靜靜地躺著,像睡著了一般膀估。 火紅的嫁衣襯著肌膚如雪幔亥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天玖像,我揣著相機與錄音紫谷,去河邊找鬼。 笑死捐寥,一個胖子當著我的面吹牛笤昨,可吹牛的內容都是我干的。 我是一名探鬼主播握恳,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瞒窒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了乡洼?” 一聲冷哼從身側響起崇裁,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎束昵,沒想到半個月后拔稳,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡锹雏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年巴比,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片礁遵。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡轻绞,死狀恐怖,靈堂內的尸體忽然破棺而出佣耐,到底是詐尸還是另有隱情政勃,我是刑警寧澤,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布兼砖,位于F島的核電站奸远,受9級特大地震影響既棺,放射性物質發(fā)生泄漏。R本人自食惡果不足惜懒叛,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一援制、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芍瑞,春花似錦、人聲如沸褐墅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妥凳。三九已至竟贯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間逝钥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留慢显,地道東北人突想。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像哗咆,于是被迫代替她去往敵國和親蜘欲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,665評論 2 354

推薦閱讀更多精彩內容