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
  // 当前事件监听数组
  let currentListeners = []
  // 下一轮事件监听数组,此处与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内部比较大,所以这里我将一些内部定义的函数拎出单独描述作用,对于其他的部分可参考中文注释内容。最后的英文注释也很好的描述了在创建store之后通过dispatch一次名字叫作INIT的action来进行整个store的内部state初始化。总结一下非函数部分内部的功能就是以下几点内容:

  1. 判断入参是否符合要求,即只能最多一个enhancer
  2. 兼容第二个参数为enhancer而没有初始state的情况
  3. 处理有enhancer的创建逻辑
  4. 判断reducer的正确性
  5. 定义内部功能变量
  6. 定义内部功能函数或提供给用户的API
  7. 初始化store,初始化state
  8. 导出API

讲完了非函数部分内容,接下来一个一个分析一下在createStore中定义的函数。

ensureCanMutateNextListeners

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

由于nextListeners变量定义的时候是根据currentListeners获得的数组引用(参考定义部分),所以为了不影响当前事件监听数组,函数ensureCanMutateNextListeners会在需要更改nextListeners的时候先判断一下是否是当前事件监听数组的引用,若是,则会用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函数主要用于添加用户事件的监听,会在dispatch更新state后进行调用(详情在dispatch部分说明),即将监听事件加入到nextListeners。需要注意的是,该函数返回的是一个用于解除监听的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获得最新的当前事件监听数组
  const listeners = (currentListeners = nextListeners)
  for (let i = 0; i < listeners.length; i++) {
    // 遍历触发监听事件
    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获取最新的事件监听数组,同时遍历触发每个监听事件;
  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
  // 触发一次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.')
      }
      // 定义一个监听state的方法
      function observeState() {
        if (observer.next) {
          // 运行observer对象的next方法,以当前store的state作为入参
          observer.next(getState())
        }
      }
      // 执行一次observerState方法
      observeState()
      // 定义解除监听的方法,并作为一个对象的属性,返回该对象
      const unsubscribe = outerSubscribe(observeState)
      return { unsubscribe }
    },
    // 获取当前对象的this指向
    [$$observable]() {
      return this
    }
  }
}

坦白说,之前从来没有接触、使用过这个api,所以对于其作用知之甚少,暂时只能通过代码层面来解读其作用,后面可以进一步了解一下。

All

index
compose
applyMiddleware
bindActionCreators
combineReducers
createStore

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容