zoukankan      html  css  js  c++  java
  • redux源码浅入浅出

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

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

     index

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

    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,结构十分的清晰

      

    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的函数设计,需要特别拿出来说说

      

    /**
     * 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

       重点,废话不多说直接上代码

    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就是典型的百行代码千行文档,也只有看了源码才能略微理解其用途和技巧。我很清楚上面的注解十分的混乱,有的地方描述肯定有误,也没能力三言两语把一个经典框架描述得清楚,敬请谅解,与君共勉。

        

  • 相关阅读:
    LeetCode Array Easy 414. Third Maximum Number
    LeetCode Linked List Medium 2. Add Two Numbers
    LeetCode Array Easy 283. Move Zeroes
    LeetCode Array Easy 268. Missing Number
    LeetCode Array Easy 219. Contains Duplicate II
    LeetCode Array Easy 217. Contains Duplicate
    LeetCode Array Easy 189. Rotate Array
    LeetCode Array Easy169. Majority Element
    LeetCode Array Medium 11. Container With Most Water
    LeetCode Array Easy 167. Two Sum II
  • 原文地址:https://www.cnblogs.com/dansingal/p/9045973.html
Copyright © 2011-2022 走看看