MyPrototypeWhat/take-down

redux源码解析

MyPrototypeWhat opened this issue · 5 comments

redux源码解析

尽量用最白的话讲明白~


目录:


createStore.js

export default function createStore(reducer, preloadedState, enhancer)

暴露createStore函数,返回:

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

createStore大致有三种使用:

createStore(reducer)
createStore(reducer,applyMiddleware(...middleware))
createStore(reducer,initialState,applyMiddleware(...middleware))

接下来就逐个分析一下~

变量声明和类型判断

if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {...}

  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {...}
    // 这里的 enhancer 是 applyMiddleware(...) 执行后的高阶函数
    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {...}

  let currentReducer = reducer //当前的reducer
  let currentState = preloadedState// 拿到当前 State
  let currentListeners = [] // 初始化 listeners 用于放置监听函数,用于保存快照供当前 dispatch 使用
  let nextListeners = currentListeners //引用传值 指向当前 listeners,在需要修改时复制出来修改为下次快照存储数据,不影响当前订阅
  let isDispatching = false// 用于标记是否正在进行 dispatch,用于控制 dispatch 依次调用不冲突

  /**
   * 这是currentListeners的浅拷贝,所以我们可以使用nextListeners作为调度时的临时列表。
   * 这样可以防止消费者在dispatch过程中 订阅/取消订阅 调用时出现任何错误
   */
  //在一段时间内始终没有新的订阅或取消订阅的情况下,nextListeners 与 currentListeners 可以共用内存
  // 确保可以改变 nextListeners。没有新的listener 可以始终用同一个引用
  function ensureCanMutateNextListeners() {
    // 需要写入新的监听之前调用,如果是指向同一个地址的话,就把nextListeners复制出来
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

类型判断,除了排除特定的type之外,还有另外一个目的就是,满足参数灵活。


getState

获取当前的state,没什么好说的

function getState() {
    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.'
          /**
           * 在Reducer执行时不能调用store.getState()
           * reducer已收到state作为参数。
           * 从顶端reducer往下递送,而不是从店里读出。
           */
      )
    }
    return currentState
  }

subscribe

向监听列表中添加监听函数,返回值为取消监听函数

如果在调用dispatch时订阅或取消订阅,则不会对当前正在进行的dispatch产生任何影响。但是,下一个dispatch调用(无论是否嵌套)将使用订阅列表的最新快照。

function subscribe(listener) {
    if (typeof listener !== 'function') {...}
    if (isDispatching) {...}

    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) { //防止多次触发
        return
      }

      if (isDispatching) {
        throw new Error(
          //在Reducer正在执行时,您不能取消对存储侦听器的订阅
          '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) //去除队列中相对应的listener
      currentListeners = null 
    }
  }
  1. 判断传入的listener是否是function,否则报错
  2. 判断dispatch状态,true则报错,在reducer中不能调用。
  3. 声明一个变量用来判断是否订阅,防止该订阅函数多次取消订阅
  4. ensureCanMutateNextListeners(),判断当前监听队列是否和临时监听队列是否相同引用,如果相同则通过slice复制出来一份,赋值给nextListeners,此举为了不混淆当前的监听队列。
  5. 将监听函数添加到临时的监听队列——nextListeners,(下文会讲)在dispatch的时候将临时监听队列同步到当前监听队列并触发。
  6. unsubscribe依旧触发ensureCanMutateNextListeners,然后找到对应的listener(在subscribe函数时形成闭包)在数组中的index,删除。
  7. currentListeners = null(较老的版本没有这句) 这句没太搞懂为什么这么写,有大佬明白的麻烦给说下,谢了~

dispatch

执行reducer之后获取最新的state,并且依次执行监听队列函数

function dispatch(action) {
    if (!isPlainObject(action)) {...}
    if (typeof action.type === 'undefined') {...}
    if (isDispatching) {...}
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action) //currentReducer 传入的reducer
    } finally {
      isDispatching = false
    }

   /**
    * 更新最新的监听对象,相当于:
      currentListeners = nextListeners
      const listeners = currentListeners
      如果再次触发订阅,则会执行ensureCanMutateNextListeners(),然后再次把nextListeners复制出来,添加监听
    */
    const listeners = (currentListeners = nextListeners) //在生成了currentState之后遍历当前的监听列表,逐个执行
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action //返回当前action
  }
  1. 判断是否是一个扁平对象
  2. 判断type是否为undefined,是则报错
  3. 判断是否正在dispatch,是则报错
  4. 设置当前派发状态为true,通过reducer处理之后或者 最新的state

监听:

nextListeners直接赋值给currentListeners,统一当前的监听列表,然后遍历执行监听函数


replaceReducer

替换reducer

function replaceReducer(nextReducer) { //替换reducer的同时会dipatch`@@redux/REPLACE${randomString()}`
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer

    /**
     * 此操作与ActionTypes.INIT具有相似的效果。
     * 新旧rootReducer中存在的任何Reducer都将收到以前的状态。这将使用旧状态树中的任何相关数据有效地填充新状态树。
     */
    dispatch({ type: ActionTypes.REPLACE }) //初始化state
  }

reducer替换之后,dispatch一下,更新state。一般不会用到


observable

目前没见过有用这个的...

function observable() {
    const outerSubscribe = subscribe //创建一个外部的订阅函数
    return {
      /**
       * 最小观察者订阅方法。
       * @param {object} 可用作观察者的任何对象。
       * 观察者对象应该有一个`next`方法
       * @returns {Subscription} 具有`unsubscribe`方法的对象,
       * 该方法可用于从存储中取消订阅可观察对象,并防止观察对象进一步发送值。
       * 
       */
      subscribe(observer) {
        if (typeof observer !== 'object' || observer === null) {
          throw new TypeError('Expected the observer to be an object.')
        }

        function observeState() { //如果传入的observer对象有next函数,就执行next()并将当前state放入函数中
          if (observer.next) {
            observer.next(getState())
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState) //给当前监听的数组加上observeState函数
        return { unsubscribe }
      },
      /**
       * Redux 内部没有用到这个方法,在测试代码 redux/test/createStore.spec.js 中有出现。
       * https://github.com/benlesh/symbol-observable
       */
      [$$observable]() {
        return this
      }
    }
  }

这部分用到了一个包symbol-observable感兴趣的可以去看下


最后dispatch({ type: ActionTypes.INIT })

创建存储时,将dispatch 'INIT',以便每个reducer返回其初始状态。这有效地填充了初始state tree。

utils

actionTypes.js/isPlainObject.js/warning.js

actionTypes.js

代码中的注释:

这些是Redux保留的私有操作类型。
对于任何未知操作,必须返回当前状态。
如果当前状态未定义,则必须返回初始状态。
不要在代码中直接引用这些操作类型。

const randomString = () => //"2.f.g.d.o.8"
  Math.random()
    .toString(36) //36进制
    .substring(7) //从index为7开始取
    .split('')
    .join('.')

const ActionTypes = {
  INIT: `@@redux/INIT${randomString()}`,
  REPLACE: `@@redux/REPLACE${randomString()}`,
  PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}

export default ActionTypes

ActionTypes有三种

  • INIT:在创建store的时候,初始化state
  • REPLACE:在替换reducer的时候,填充新state树
  • PROBE_UNKNOWN_ACTION:在combineReducer.js中使用,随机类型探测(combineReducer.js中有讲)

isPlainObject.js

isPlainObject函数是redux自己用来判断传递给reducer的action对象是一个plain object,也就是通过字面量方式或者Object构造函数的方式生成的对象,中间没有发生其他的继承情况

export default function isPlainObject(obj) {
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    //普通的对象while循环结束后proto的值是:Object.prototype,通过Object.create(null)生成的对象proto的值是:null
    proto = Object.getPrototypeOf(proto)

  }

  return Object.getPrototypeOf(obj) === proto
}

通过Object.getPrototypeOf(obj)自己的原型对比的原因:
防止一些边界情况,例如例如跨frame访问变量时
在一个frame里面调用父窗口的函数:
window.parent.someFunction(["hello", "world"])
在父窗口中有someFunction的定义:

function someFunction(arg) {
    if (arg instanceof Array) {
      // ... operate on the array
    }
  }

因为两段代码所处的javascript执行环境是不一样的,每个frame都有自己的执行环境,也就是说两个执行环境中的Array构造函数都是不等的,那么if语句的判断就为false


warning.js

用来抛出错误,没什么好说的

export default function warning(message) {
  /* eslint-disable no-console */
  if (typeof console !== 'undefined' && typeof console.error === 'function') {
    console.error(message)
  }
  /* eslint-enable no-console */
  try {
    throw new Error(message)
  } catch (e) {} // eslint-disable-line no-empty
}

applyMiddleware.js

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    // 此时...arg=(reducer, preloadedState)
    const store = createStore(...args) 
    // 此时 store={dispatch,subscribe,getState,replaceReducer,[$$observable]: observable}

    let dispatch = () => {...} //防止在构建期间dispatch
      

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    // chain=[next=>action=>....]
    const chain = middlewares.map(middleware => middleware(middlewareAPI)) 
    /**
     * 给每个中间件加入middlewareAPI
     * 如 chian = [middleware1, middleware2, middleware3]
     */
    dispatch = compose(...chain)(store.dispatch)
    /**
     * 通过compose函数传入chain一层层增强dispatch
     * dispatch = compose(...chain)(store.dispatch),即执行 middleware1(middleware2(middleware3(store.dispatch)))
     */

    return {
      ...store,
      dispatch //通过中间件增强之后的dipatch
    }
  }
}

常用方法createStore(reducer,applyMiddleware(...middleware))

执行之后返回:

(createStore)=>(...args)=>{...; return{...store,dispatch //通过中间件增强之后的dipatch}}

精简下来之后看着就清晰了。

  1. createStore就是上文createStore.js中的createStore,用来生成store

  2. ...args就是(reducer, preloadedState),

  3. ...args就是(reducer, preloadedState),

  4. let dispatch函数,防止中间件在构造中dispatch

  5. 声明middlewareAPI注入中间件中,通过map,将各个中间件执行一遍之后返回一个数组,也是就是chain变量

  6. 通过compose(下文讲)将dispatch加强之后,将createStore中生成的store中的dispatch替换返回。

compose.js

类似koa2洋葱圈,依次加强dispatch

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))

函数式编程,整体代码很短...

源代码注释:Composes single-argument functions from right to left. The rightmost function can take multiple arguments as it provides the signature for the resulting composite function.(从右到左组成单参数函数。最右边的函数可以接受多个参数,因为它为生成的复合函数提供签名。)

每次循环返回一个函数 (...args) => a(b(...args)),所以每当一次循环结束之后a=(...args) => a(b(...args))
例如[f,g,h].reduce((total,item)=>(...args) => total(item(...args)))
reduce如果没有初始值,第一位参数取数组的第一项

则:第一次循环:total=(...args) =>f(g(...args))

​ 第二次循环:total=(...args) =>f(g(h(...args)))


bindActionCreators.js

作用是将单个或多个ActionCreator转化为dispatch(action)的函数集合形式,简化书写

bindActionCreators之后的action

boundActionCreators={
 action1:function() {return dispatch(actionCreator.apply(this, arguments))},
 action2:function() {return dispatch(actionCreator.apply(this, arguments))},
 action3:function() {return dispatch(actionCreator.apply(this, arguments))},
}
function bindActionCreator(actionCreator, dispatch) { //此函数就是返回一个function,并且帮你dispatch
  return function() { 
    return dispatch(actionCreator.apply(this, arguments))
  }
}

export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    /**
     * 如果是function则返回一个
     * function() { 
          return dispatch(actionCreator.apply(this, arguments))
        }
        actionCreator应当接收参数并返回一个action
     */
    return bindActionCreator(actionCreators, dispatch) 
  }

  if (typeof actionCreators !== 'object' || actionCreators === null) {...}

  const boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key] //遍历拿到dispatch函数
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

不使用bindActionCreators时候:

function actions(dispatch) {

 return {

  onIncrement: () => dispatch(increment())

 };

}

使用bindActionCreators

 let actions = {

  addItem: ()=>({type: types.ADD_ITEM,paload}) 

 }
 bindActionCreators(actions, dispatch); 

combineReducers.js

将多个reducer合成一个,并且改变state的结构

export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {...}
    }

    if (typeof reducers[key] === 'function') { //过滤掉不是function的
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  // This is used to make sure we don't warn about the same
  // 这是用来确保我们不会发出同样的警告
  // keys multiple times.
  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    assertReducerShape(finalReducers) //遍历执行两次数组内的reducer,对返回值进行判断
  } catch (e) {
    shapeAssertionError = e
  }

上面一系列操作,干了几件事:

  1. 生成finalReducers{reducer函数名:reducer}
  2. 根据finalReducers拿到所有键名生成finalReducerKeys
  3. 执行assertReducerShape

assertReducerShape

遍历执行所有的reducer,进行返回类型的判断

function assertReducerShape(reducers) { 
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    const initialState = reducer(undefined, { type: ActionTypes.INIT })

    if (typeof initialState === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined during initialization. ` +
          `If the state passed to the reducer is undefined, you must ` +
          `explicitly return the initial state. The initial state may ` +
          `not be undefined. If you don't want to set a value for this reducer, ` +
          `you can use null instead of undefined.`
          /**
           * 在初始化期间返回undefined。
           * 如果传递给reducer的state未定义,则必须显式返回初始状态。
           * 初始状态不能是未定义的。如果不想为此reducer设置值,可以使用NULL而不是undefined。`
           */
      )
    }

    if (
      typeof reducer(undefined, {
        type: ActionTypes.PROBE_UNKNOWN_ACTION()
      }) === 'undefined'
    ) {
      throw new Error(
        `Reducer "${key}" returned undefined when probed with a random type. ` +
          `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
          `namespace. They are considered private. Instead, you must return the ` +
          `current state for any unknown actions, unless it is undefined, ` +
          `in which case you must return the initial state, regardless of the ` +
          `action type. The initial state may not be undefined, but can be null.`
          /**
           * 使用随机类型探测时,reducer“${key}”返回undefined。
           * 不要试图在“redux/*”名称空间中处理${ActionTypes.INIT}或其他操作。
           * 他们被认为是私人的。相反,您必须返回任何未知操作的当前状态,除非未定义,
           * 在这种情况下,无论操作类型如何,都必须返回初始状态。初始状态不能未定义,但可以为空。
           */
      )
    }
  })
}

通过该函数对类型的判断之后,

再返回看剩下的combineReducers函数

return function combination(state = {}, action) {//作为createStore中reducer参数
    if (shapeAssertionError) {...}

    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    let hasChanged = false //判断state是否改变
    const nextState = {}
    /**
     * 每次dispatch的时候就会遍历执行所有的reducer
     */
    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)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state //改变了则返回改变之后的state,否则返回之前的state
  }

combination就是createStorereducer参数,就是将所有reducer整合到这个函数.来看看怎么实现的

  1. 首先判断上文的类型判断是否通过
  2. 执行getUnexpectedStateShapeWarningMessage,获取warningMessage,如果存在warningMessage直接报错
  3. 声明hasChanged,用来判断state是否更新
  4. 遍历所有的reducer,拿到上一次的state,放入reducer执行,拿到执行后的state,如果为undefined,报错,生成新的state之后,和上一个state进行浅比较,判断state是否更改
  5. state改变了则返回改变之后的state,否则返回之前的state

getUnexpectedStateShapeWarningMessage

判断是否有意料之外的state类型

function getUnexpectedStateShapeWarningMessage(
  inputState,
  reducers,
  action,
  unexpectedKeyCache
) {
  const reducerKeys = Object.keys(reducers)
  const argumentName =
    action && action.type === ActionTypes.INIT //判断是初始化时reducer,还是之后的reducer
      ? 'preloadedState argument passed to createStore'//传递给createStore的preloadedState参数
      : 'previous state received by the reducer'//reducer接收的先前状态

  if (reducerKeys.length === 0) {//Store没有有效的reducer。确保传递给comineReducers的参数是一个值为Reducers的对象}

  if (!isPlainObject(inputState)) {...}

  /**
   * 获取state上未被reducer处理的状态的键值unexpectedKeys,并将其存入cache值中。
   */
  const unexpectedKeys = Object.keys(inputState).filter(
    //如果reducer的属性中没有state,并且unexpectedKeyCache中没有对应的值
    key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
  )

  unexpectedKeys.forEach(key => {
    unexpectedKeyCache[key] = true
  })
/**
 * 检测是否为内置的replace action,因为当使用store的replaceReducer时会自动触发该内置action,
 * 并将reducer替换成传入的,此时检测的reducer和原状态树必然会存在冲突,
 * 所以在这种情况下检测到的unexpectedKeys并不具备参考价值,将不会针对性的返回抛错信息,反之则会返回。
 */
  if (action && action.type === ActionTypes.REPLACE) return

  if (unexpectedKeys.length > 0) {
    return (
      `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
      `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
      `Expected to find one of the known reducer keys instead: ` +
      `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
    )
  }
}

getUndefinedStateErrorMessage

function getUndefinedStateErrorMessage(key, action) {
  const actionType = action && action.type
  const actionDescription =
    (actionType && `action "${String(actionType)}"`) || 'an action'

  return (
    `Given ${actionDescription}, reducer "${key}" returned undefined. ` +
    `To ignore an action, you must explicitly return the previous state. ` +
    `If you want this reducer to hold no value, you can return null instead of undefined.`
  )
}