Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

redux源码解析 #6

Open
MyPrototypeWhat opened this issue Oct 17, 2019 · 5 comments
Open

redux源码解析 #6

MyPrototypeWhat opened this issue Oct 17, 2019 · 5 comments

Comments

@MyPrototypeWhat
Copy link
Owner

MyPrototypeWhat commented Oct 17, 2019

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。

@MyPrototypeWhat
Copy link
Owner Author

MyPrototypeWhat commented Oct 17, 2019

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
}

@MyPrototypeWhat
Copy link
Owner Author

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替换返回。

@MyPrototypeWhat
Copy link
Owner Author

MyPrototypeWhat commented Oct 17, 2019

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)))


@MyPrototypeWhat
Copy link
Owner Author

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); 

@MyPrototypeWhat
Copy link
Owner Author

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.`
  )
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant