redux源码浅入浅出

  运用redux有一段时间了,包括redux-thunk和redux-saga处理异步action都有必定的涉及,如今技术栈转向阿里的dva+antd,好用得不要不要的,可是须要知己知彼要对react家族有一点源码上的深刻了,就从redux开始吧。javascript

  redux源码是那么简洁、清晰、干净,让我忍不住一口气所有看完了还意犹未尽的写一篇随笔,mark一下,过段时间回头再从新细细评味学习一波。原谅我把整片代码贴出来,由于我懒啊,我会尽可能把代码注解写详细一点。java

 index

   redux对外暴露出的api,这里能够看出文件结构和功能块相关分得很清晰。react

import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'

/*
 * This is a dummy function to check if the function name has been altered by minification.
 * If the function has been minified and NODE_ENV !== 'production', warn the user.
 */
// isCrushed 函数仅仅用于判断代码是否处于压缩并再压缩时抛出警告
function isCrushed() {}

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning(
    'You are currently using minified code outside of NODE_ENV === "production". ' +
      'This means that you are running a slower development build of Redux. ' +
      'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
      'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
      'to ensure you have the correct code for your production build.'
  )
}

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

  createStore

   redux最重要的api,用于构建Store,并在建立之后还有本身的api,结构十分的清晰webpack

  

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

/**
 * Creates a Redux store that holds the state tree.
 * The only way to change the data in the store is to call `dispatch()` on it.
 *
 * There should only be a single store in your app. To specify how different
 * parts of the state tree respond to actions, you may combine several reducers
 * into a single reducer function by using `combineReducers`.
 *
 * @param {Function} reducer A function that returns the next state tree, given
 * the current state tree and the action to handle.
 * @param {Function} reducer:返回一个完整独立全新的state tree,接受参数(当前state,须要触发actions集合)
 *
 * @param {any} [preloadedState] The initial state. You may optionally specify it
 * to hydrate the state from the server in universal apps, or to restore a
 * previously serialized user session.
 * If you use `combineReducers` to produce the root reducer function, this must be
 * an object with the same shape as `combineReducers` keys.
 * @param {any} [preloadedState] 初始化state,不是必需,能够与服务端渲染水合初始状态,
 * 若是使用combineReduers必需与其中key值一一对应,查看combineReduers实现
 *
 * @param {Function} [enhancer] The store enhancer. You may optionally specify it
 * to enhance the store with third-party capabilities such as middleware,
 * time travel, persistence, etc. The only store enhancer that ships with Redux
 * is `applyMiddleware()`.
 * @param {Function} [enhancer] store的外挂,经常使用middleware中间件,其余暂时不去深刻
 *
 * @returns {Store} A Redux store that lets you read the state, dispatch actions
 * and subscribe to changes.
 */
export default function createStore(reducer, preloadedState, enhancer) {

  // 判断参数个数,相似jq===on参数处理方式
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  // 首先判断enhancer(常见的即是middlewares中间件),循环回调将跳过此处
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    // middlewares详细解释返回值,
    return enhancer(createStore)(reducer, preloadedState)
  }
  // redux为了方便开发者作了不少友好的提示,只有深刻源码才知道的良苦用心,reducer只接受是一个函数
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  // 保存当前的传入值,后边会涉及到这些值的来回更迭
  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  // 监听函数事件队列 为何不写成let nextListeners = currentListeners = [] 风格吗?
  // 还有为何须要两个listener数组来存放呢?答案再订阅和dispatch里面
  let nextListeners = currentListeners
  // 是否处于dispatch过程当中,我也好奇异步dispatch的时候将怎么变化
  let isDispatching = false

  // 当前监听队列与接下来的监听队列指向同一个数组时,slice出新的数组
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      // 仍是为了去除引用,完成next和current的交替,能够将next看做是current的快照
      nextListeners = currentListeners.slice()
    }
  }

  /**
   * Reads the state tree managed by the store.
   *
   * @returns {any} The current state tree of your application.
   */
  // 只有在非触发状态才能经过api获取当前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.'
      )
    }
    // 注意这里闭包了,直接给里currentState,且他是时常变化的值,须要再稳定的时候取值
    return currentState
  }

  /**
   * Adds a change listener. It will be called any time an action is dispatched,
   * and some part of the state tree may potentially have changed. You may then
   * call `getState()` to read the current state tree inside the callback.
   *
   * You may call `dispatch()` from a change listener, with the following
   * caveats:
   *
   * 1. The subscriptions are snapshotted just before every `dispatch()` call.
   * If you subscribe or unsubscribe while the listeners are being invoked, this
   * will not have any effect on the `dispatch()` that is currently in progress.
   * However, the next `dispatch()` call, whether nested or not, will use a more
   * recent snapshot of the subscription list.
   *
   * 2. The listener should not expect to see all state changes, as the state
   * might have been updated multiple times during a nested `dispatch()` before
   * the listener is called. It is, however, guaranteed that all subscribers
   * registered before the `dispatch()` started will be called with the latest
   * state by the time it exits.
   *
   * @param {Function} listener A callback to be invoked on every dispatch.
   * @returns {Function} A function to remove this change listener.
   */
  // dva里面也有监听器,下次去看看源码
  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
    // 函数入其名,获得nextListeners
    ensureCanMutateNextListeners()
    // 将监听的事件添加到nextListeners队列中,注意可能添加了队列中已有的事件,无论执行两遍
    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
      // 再次获得新的nextListeners
      ensureCanMutateNextListeners()
      // 感受这里若是注册两个相同的事件,会移除前面那个,不知道会不会有问题
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  /**
   * Dispatches an action. It is the only way to trigger a state change.
   *
   * The `reducer` function, used to create the store, will be called with the
   * current state tree and the given `action`. Its return value will
   * be considered the **next** state of the tree, and the change listeners
   * will be notified.
   *
   * The base implementation only supports plain object actions. If you want to
   * dispatch a Promise, an Observable, a thunk, or something else, you need to
   * wrap your store creating function into the corresponding middleware. For
   * example, see the documentation for the `redux-thunk` package. Even the
   * middleware will eventually dispatch plain object actions using this method.
   *
   * @param {Object} action A plain object representing “what changed”. It is
   * a good idea to keep actions serializable so you can record and replay user
   * sessions, or use the time travelling `redux-devtools`. An action must have
   * a `type` property which may not be `undefined`. It is a good idea to use
   * string constants for action types.
   *
   * @returns {Object} For convenience, the same action object you dispatched.
   *
   * Note that, if you use a custom middleware, it may wrap `dispatch()` to
   * return something else (for example, a Promise you can await).
   */
  // 至关重要的方法,纯粹的dispatch的参数只接受Object类型的,thunk就是对它进行处理进而能传入
  // function用回调的形式从新dispatch,下次再详细thunk和saga
  function dispatch(action) {
    // isPlainObject用于判断是不是对象
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
      )
    }
    // action关键字限制为 type,为了避免形成命名上的困惑通常type前缀我会设置与文件夹同名
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }

    // 正在dispatch,这里什么状况会出现这个警告呢!!!
    // 在dispatch中嵌套的调用dispatch会触发这类警告,多是担忧dispatchA(dispatchB(dispatchA))的嵌套循环问题把
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      // 进行reduce操做,记得参数是当前state和action对象,返回全新的State对象,这一手操做是react就高兴了
      currentState = currentReducer(currentState, action)
    } finally {
      //完成一波reducer记得复位标志,表示个人完成dispatch。 
      isDispatching = false
    }

    // 执行事件队列前才拿到最新的listenters,在此以前可能会出现订阅与退订的嵌套等问题,暂存的nextlisteners能够保证dispatch的正常执行
    // 假如出现listenerA(){store.subscribe(listenerA);}的嵌套状况,listeners的长度将再每一次执行延长一直至无限长
    // 固然若是采用len = listeners.length;直接固定循环次数能够解决如今的状况,可是退订等事件的发生也会出现问题,因此暂存是最安全的作法
    const listeners = (currentListeners = nextListeners)
    // 为何要用for循环不用foreach,想一想forEach对空元素的处理的性能问题把
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      // 为何不直接listeners[i]()执行呢?而是负值单独调用呢?
      // 赋值以后this的指向再也不是listens而是window
      listener()
    }

    // 返回了整个action对象
    return action
  }

  /**
   * Replaces the reducer currently used by the store to calculate the state.
   *
   * You might need this if your app implements code splitting and you want to
   * load some of the reducers dynamically. You might also need this if you
   * implement a hot reloading mechanism for Redux.
   *
   * @param {Function} nextReducer The reducer for the store to use instead.
   * @returns {void}
   */

  // 替换reducer函数
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    // 触发私有的replace action
    dispatch({ type: ActionTypes.REPLACE })
  }

  /**
   * Interoperability point for observable/reactive libraries.
   * @returns {observable} A minimal observable of state changes.
   * For more information, see the observable proposal:
   * https://github.com/tc39/proposal-observable
   */
  // 能够看做是对redux观察者的一个扩展,可做为全局的每次dispatch都执行方法入口
  function observable() {
    const outerSubscribe = 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.
       */
      // 须要传入一个带next方法的对象,将返回退订钩子
      subscribe(observer) {
        if (typeof observer !== 'object' || observer === null) {
          throw new TypeError('Expected the observer to be an object.')
        }

        function observeState() {
          if (observer.next) {
            // next方法将得到当时的store
            observer.next(getState())
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        // 返回包含退订对象
        return { unsubscribe }
      },
      // 用于获取observeable,这名字取的。。。
      [$$observable]() {
        return this
      }
    }
  }

  // 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.
  // 初始化store对象
  dispatch({ type: ActionTypes.INIT })

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

  

  compose

   一个关于reduce的函数设计,须要特别拿出来讲说git

  

/**
 * 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.
 *
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (...args) => f(g(h(...args))).
 */

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

  if (funcs.length === 1) {
    return funcs[0]
  }
  // reduce用在这里太巧妙了,奇妙的洋葱函数,好吧也没那么奇妙
  // compose(f, g, h)(...args) 等同于 f(g(h(...args)))在后面会造成一个currying函数
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

  

  applyMiddleware

   重点,废话很少说直接上代码github

import compose from './compose'

/**
 * Creates a store enhancer that applies middleware to the dispatch method
 * of the Redux store. This is handy for a variety of tasks, such as expressing
 * asynchronous actions in a concise manner, or logging every action payload.
 *
 * See `redux-thunk` package as an example of the Redux middleware.
 *
 * Because middleware is potentially asynchronous, this should be the first
 * store enhancer in the composition chain.
 *
 * Note that each middleware will be given the `dispatch` and `getState` functions
 * as named arguments.
 *
 * @param {...Function} middlewares The middleware chain to be applied.
 * @returns {Function} A store enhancer applying the middleware.
 */
// 很是精髓的一段代码
// createStore中以enhancer(createStore)(reducer, preloadedState)调用
export default function applyMiddleware(...middlewares) {
    // 二阶函数参数...args对应reducer, preloadedState
    return createStore => (...args) => {
        const store = createStore(...args)
        // 这里不该该是 const dispatch = store.dispatch??有些版本出现这样
        // 猜想:这里避免使用到没有中间件处理过的disptch,后面将传入完整的store.dispatch做为根参数,
        // 求解若是这里只是个警告函数,每一个中间件接受到的({ dispatch, getState })又是什么呢?
        // 好吧,我又又想到答案了,再下面
        let dispatch = () => {
            throw new Error(
                `Dispatching while constructing your middleware is not allowed. ` +
                `Other middleware would not be applied to this dispatch.`
            )
        }
        // 中间件获取到的能力,获取store快照(isDispatching???怎么判断的),触发reducer
        const middlewareAPI = {
            getState: store.getState,
            // 我就是上面的答案:这里dispatch用闭包并非直接的引用,dispatch会根据dispatch = compose(...chain)(store.dispatch)
            // 而变化,在此以前调用dispatch会爆出警告!!!
            dispatch: (...args) => dispatch(...args)
        }
        // middleware应该是高阶函数,return 了一个function在chain数组
        // 对应thunk的createThunkMiddleware({dispatch, getStat}),这里只要注意传入了什么,thunk内详细分析怎么运行中间件
        const chain = middlewares.map(middleware => middleware(middlewareAPI))
        // 将store.dispatch做为二阶参数传入,最终将对应中间件最内层的action,
        // 注意下面这个例子:
        // applyMiddleware(log1, log2, log3),在这里经过洋葱函数的处理dispatch变成log11(log22(log33(store.dispatch)))这样一个函数
        // log11是log1({dispatch, getState})的返回函数,以此类推,这种结构也限定里中间件函数的基本结构是
        // ({ dispatch, getState }) => next => action => {} ,最开始可能对这个结构很迷糊,why,看下面
     // 这里就造成一个第一个参数为store.dispatch的currying函数,以后传入的action,dispatch(action)都将一并视为compose(...chain)(store.dispatch)(action)
dispatch = compose(...chain)(store.dispatch) // 对应返回在了createStore里即Store,全新的dispatch诞生 return { ...store, dispatch } } } // 以为把redux-thunk的代码一块儿贴出来才有参照性 function createThunkMiddleware(extraArgument) { // 其实thunk内容实在是简洁,判断类型将dispatch放入到函数里面,这里的dispatch是层层包装过的 // 那么咱们来分析针对整个箭头函数和中间件结构进行分析一下 // log11的next(action)对应log22(log33(action)), // log22的next(action)对应log33(action), // log33的next对应store.dispatch,最后返回一个须要传参为action的函数,
// action对应{type: 'TO_DO',text: '1'}一直传递无变化,只有next变化,造成一个层层执行 // // 而执行顺序有点像冒泡,从外到里再从里到外,若是上面的log每一个都有before和after的话,顺序将是 // log11.before > log22.before > log33.before > store.dispatch > log33.after > log22.after > log11.after > end // 每个中间件将对dispatch以前和以后做些动做 return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; // export default thunk; // 调用方式 createStore(reducer, applyMiddleware(thunk))

  其余还有几个文件就不贴出来了,只有深刻到源码才能感觉代码之美,redux简直称为精粹。redux就是典型的百行代码千行文档,也只有看了源码才能略微理解其用途和技巧。我很清楚上面的注解十分的混乱,有的地方描述确定有误,也没能力三言两语把一个经典框架描述得清楚,敬请谅解,与君共勉。web

相关文章
相关标签/搜索