zoukankan      html  css  js  c++  java
  • redux的源码解析

    一、 redux出现的动机

    1. Javascript 需要管理比任何时候都要多的state
    2. state 在什么时候,由于什么原因,如何变化已然不受控制。
    3. 来自前端开发领域的新需求
    4. 我们总是将两个难以理清的概念混淆在一起:变化和异步。
    5. Redux 视图让state 的变化变得可预测。

    二、 核心概念

     1. 想要更新state中的数据,你需要发起一个action,Action就是一个普通的JavaScript 对象用来描述发生了什么。为了把actin 和state串起来开发一些函数,就是redcer。

    三、 三大原则

    1. 单一数据源 整个应用的state被存储在一棵objecttree中, 并且这个 object tree 只 存在于一个唯一的store 中。

    2. state 是只读的,唯一改变state的方法就是触发action,action 是一个用于描述已发 生事件的普通对象。(确保了视图和网络请求不能直接修改state,只能表达想要修改的意图)

    3. 使用纯函数来执行修改为了描述action如何改变state tree ,你需要编写reducers。

    四、 源码解析

    1. 入口文件index.js
    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.
     */
    
    // 判断文件是否被压缩了
    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.'
      )
    }
    /*
      从入口文件可以看出 redux 对外暴露了5个API。
      createStore ,
      combineReducers,
      bindActionCreators,
     applyMiddleware,
     compose,
     */
    export {
      createStore,
      combineReducers,
      bindActionCreators,
      applyMiddleware,
      compose,
      __DO_NOT_USE__ActionTypes
    }
    2. 对外暴露的第一个API createStore => createStore.js
      1 import $$observable from 'symbol-observable'
      2 
      3 import ActionTypes from './utils/actionTypes'
      4 import isPlainObject from './utils/isPlainObject'
      5 
      6 /**
      7  * Creates a Redux store that holds the state tree.
      8  * The only way to change the data in the store is to call `dispatch()` on it.
      9  *
     10  * There should only be a single store in your app. To specify how different
     11  * parts of the state tree respond to actions, you may combine several reducers
     12  * into a single reducer function by using `combineReducers`.
     13  *
     14  * @param {Function} reducer A function that returns the next state tree, given
     15  * the current state tree and the action to handle.
     16  *
     17  * @param {any} [preloadedState] The initial state. You may optionally specify it
     18  * to hydrate the state from the server in universal apps, or to restore a
     19  * previously serialized user session.
     20  * If you use `combineReducers` to produce the root reducer function, this must be
     21  * an object with the same shape as `combineReducers` keys.
     22  *
     23  * @param {Function} [enhancer] The store enhancer. You may optionally specify it
     24  * to enhance the store with third-party capabilities such as middleware,
     25  * time travel, persistence, etc. The only store enhancer that ships with Redux
     26  * is `applyMiddleware()`.
     27  *
     28  * @returns {Store} A Redux store that lets you read the state, dispatch actions
     29  * and subscribe to changes.
     30  */
     31 /*
     32   从源码上可以看出 createStore 是一个函数。接收三个参数 reducer, preloadeState, enhancer
     33  */
     34 export default function createStore(reducer, preloadedState, enhancer) {
     35   // 如果 preloadeState 是一个函数 && enhancer未定义preloadeState 和 enhancer交换位置
     36   if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
     37     enhancer = preloadedState
     38     preloadedState = undefined
     39   }
     40   //
     41   if (typeof enhancer !== 'undefined') {
     42     if (typeof enhancer !== 'function') {
     43       throw new Error('Expected the enhancer to be a function.')
     44     }
     45     // 上面两个判断是为了确保 enchancer是个函数
     46     return enhancer(createStore)(reducer, preloadedState)
     47   }
     48 
     49   // reducer必须是 个函数,如果不是个函数给出友好的 提示
     50   if (typeof reducer !== 'function') {
     51     throw new Error('Expected the reducer to be a function.')
     52   }
     53 
     54   let currentReducer = reducer    // 把reducer 暂存起来
     55   let currentState = preloadedState // 把preloadeState暂存起来
     56   let currentListeners = []
     57   let nextListeners = currentListeners
     58   let isDispatching = false   //判断是否正处于dispatch中
     59 
     60   // 如果 nextListeners 和 currrentListeners 都指向一个内存空间的时候, 深复制一份出来。确保两个之间不会相互影响。
     61   function ensureCanMutateNextListeners() {
     62     if (nextListeners === currentListeners) {
     63       nextListeners = currentListeners.slice()
     64     }
     65   }
     66 
     67   /**
     68    * Reads the state tree managed by the store.
     69    *
     70    * @returns {any} The current state tree of your application.
     71    */
     72   // 获取目前的 state的值。
     73   function getState() {
     74     if (isDispatching) {
     75       throw new Error(
     76         'You may not call store.getState() while the reducer is executing. ' +
     77           'The reducer has already received the state as an argument. ' +
     78           'Pass it down from the top reducer instead of reading it from the store.'
     79       )
     80     }
     81 
     82     return currentState
     83   }
     84 
     85   /**
     86    * Adds a change listener. It will be called any time an action is dispatched,
     87    * and some part of the state tree may potentially have changed. You may then
     88    * call `getState()` to read the current state tree inside the callback.
     89    *
     90    * You may call `dispatch()` from a change listener, with the following
     91    * caveats:
     92    *
     93    * 1. The subscriptions are snapshotted just before every `dispatch()` call.
     94    * If you subscribe or unsubscribe while the listeners are being invoked, this
     95    * will not have any effect on the `dispatch()` that is currently in progress.
     96    * However, the next `dispatch()` call, whether nested or not, will use a more
     97    * recent snapshot of the subscription list.
     98    *
     99    * 2. The listener should not expect to see all state changes, as the state
    100    * might have been updated multiple times during a nested `dispatch()` before
    101    * the listener is called. It is, however, guaranteed that all subscribers
    102    * registered before the `dispatch()` started will be called with the latest
    103    * state by the time it exits.
    104    *
    105    * @param {Function} listener A callback to be invoked on every dispatch.
    106    * @returns {Function} A function to remove this change listener.
    107    */
    108   // 经典的订阅函数
    109   function subscribe(listener) {
    110     if (typeof listener !== 'function') {
    111       throw new Error('Expected the listener to be a function.')
    112     }
    113 
    114     if (isDispatching) {
    115       throw new Error(
    116         'You may not call store.subscribe() while the reducer is executing. ' +
    117           'If you would like to be notified after the store has been updated, subscribe from a ' +
    118           'component and invoke store.getState() in the callback to access the latest state. ' +
    119           'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
    120       )
    121     }
    122     // 闭包的经典应用 每次订阅一个事件的时候,都有一个内部状态, 用于后续的取消订阅
    123     let isSubscribed = true
    124     // 深复制一份监听对象
    125     ensureCanMutateNextListeners()
    126     // 把每个监听对象都放置于一个数组中,保存下来,(精华之处,对闭包的使用登峰造极)
    127     nextListeners.push(listener)
    128     // 当注册一个监听事件的返回一个函数,调用这个函数可以取消订阅,具体操作方法就是从监听的数组中移出掉。
    129     return function unsubscribe() {
    130       // 防止重复取消订阅
    131       if (!isSubscribed) {
    132         return
    133       }
    134 
    135       if (isDispatching) {
    136         throw new Error(
    137           'You may not unsubscribe from a store listener while the reducer is executing. ' +
    138             'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
    139         )
    140       }
    141       // 对应上面那条,防止重复取消订阅
    142       isSubscribed = false
    143 
    144       ensureCanMutateNextListeners()
    145       const index = nextListeners.indexOf(listener)
    146         // 删除数组中某一项的方法 splice
    147       nextListeners.splice(index, 1)
    148     }
    149   }
    150 
    151   /**
    152    * Dispatches an action. It is the only way to trigger a state change.
    153    *
    154    * The `reducer` function, used to create the store, will be called with the
    155    * current state tree and the given `action`. Its return value will
    156    * be considered the **next** state of the tree, and the change listeners
    157    * will be notified.
    158    *
    159    * The base implementation only supports plain object actions. If you want to
    160    * dispatch a Promise, an Observable, a thunk, or something else, you need to
    161    * wrap your store creating function into the corresponding middleware. For
    162    * example, see the documentation for the `redux-thunk` package. Even the
    163    * middleware will eventually dispatch plain object actions using this method.
    164    *
    165    * @param {Object} action A plain object representing “what changed”. It is
    166    * a good idea to keep actions serializable so you can record and replay user
    167    * sessions, or use the time travelling `redux-devtools`. An action must have
    168    * a `type` property which may not be `undefined`. It is a good idea to use
    169    * string constants for action types.
    170    *
    171    * @returns {Object} For convenience, the same action object you dispatched.
    172    *
    173    * Note that, if you use a custom middleware, it may wrap `dispatch()` to
    174    * return something else (for example, a Promise you can await).
    175    */
    176   // 派发一个事件
    177   function dispatch(action) {
    178     // p、判断action是否是个对象
    179     if (!isPlainObject(action)) {
    180       throw new Error(
    181         'Actions must be plain objects. ' +
    182           'Use custom middleware for async actions.'
    183       )
    184     }
    185     // 严格控制 action 的书写格式  { type: 'INCREMENT'}
    186     if (typeof action.type === 'undefined') {
    187       throw new Error(
    188         'Actions may not have an undefined "type" property. ' +
    189           'Have you misspelled a constant?'
    190       )
    191     }
    192 
    193     if (isDispatching) {
    194       throw new Error('Reducers may not dispatch actions.')
    195     }
    196     // isDipatching 也是闭包的经典用法
    197       /*
    198         触发 dispatch的时候 把 isDispatching 改为 true。 照应全篇中对 dispatching 正在触发的时候的判断
    199         finally 执行完毕的时候 置为 false
    200        */
    201     try {
    202       isDispatching = true
    203       // 获取最新 的state 值。 currentState 经典之处闭包
    204       currentState = currentReducer(currentState, action)
    205     } finally {
    206       isDispatching = false
    207     }
    208 
    209     // 对监听对象从新赋值 其实里面每个listener都是一个函数。 于subscribe相对应
    210     // 当每发生一个dispatch 事件的时候, 都循环调用,触发监听事件
    211     const listeners = (currentListeners = nextListeners)
    212     for (let i = 0; i < listeners.length; i++) {
    213       const listener = listeners[i]
    214       listener()
    215     }
    216 
    217     return action
    218   }
    219 
    220   /**
    221    * Replaces the reducer currently used by the store to calculate the state.
    222    *
    223    * You might need this if your app implements code splitting and you want to
    224    * load some of the reducers dynamically. You might also need this if you
    225    * implement a hot reloading mechanism for Redux.
    226    *
    227    * @param {Function} nextReducer The reducer for the store to use instead.
    228    * @returns {void}
    229    */
    230   // 替换 reducer 用 新的 reducer替换以前的reducer 参数同样必须是函数
    231   function replaceReducer(nextReducer) {
    232     if (typeof nextReducer !== 'function') {
    233       throw new Error('Expected the nextReducer to be a function.')
    234     }
    235 
    236     currentReducer = nextReducer
    237     dispatch({ type: ActionTypes.REPLACE })
    238   }
    239 
    240   /**
    241    * Interoperability point for observable/reactive libraries.
    242    * @returns {observable} A minimal observable of state changes.
    243    * For more information, see the observable proposal:
    244    * https://github.com/tc39/proposal-observable
    245    */
    246   // 观察模式
    247   function observable() {
    248     const outerSubscribe = subscribe
    249     return {
    250       /**
    251        * The minimal observable subscription method.
    252        * @param {Object} observer Any object that can be used as an observer.
    253        * The observer object should have a `next` method.
    254        * @returns {subscription} An object with an `unsubscribe` method that can
    255        * be used to unsubscribe the observable from the store, and prevent further
    256        * emission of values from the observable.
    257        */
    258       subscribe(observer) {
    259         if (typeof observer !== 'object' || observer === null) {
    260           throw new TypeError('Expected the observer to be an object.')
    261         }
    262 
    263         function observeState() {
    264           if (observer.next) {
    265             observer.next(getState())
    266           }
    267         }
    268 
    269         observeState()
    270         const unsubscribe = outerSubscribe(observeState)
    271         return { unsubscribe }
    272       },
    273 
    274       [$$observable]() {
    275         return this
    276       }
    277     }
    278   }
    279 
    280   // When a store is created, an "INIT" action is dispatched so that every
    281   // reducer returns their initial state. This effectively populates
    282   // the initial state tree.
    283     // 触发一state 树
    284   dispatch({ type: ActionTypes.INIT })
    285   /*
    286      由此可以看出 调用 createStore(); 后。对外暴露的方法
    287      1. dispatch
    288      2. subscribe
    289      3. getState
    290      4. replaceReducer
    291      5.观察模式
    292      const store = createStore(reducer, preloadedState, enchancer);
    293      store.dispatch(action);
    294      store.getState(); // 为什么这个方法能够获得 state的值。因为 currentState 的闭包实现。
    295      store.subscribe(() => console.log(store.getState()));
    296      store.replaceReducer(reducer);
    297      总结:纵观createStore方法的实现,其实都是建立在闭包的基础之上。可谓是把闭包用到了极致。
    298    */
    299   return {
    300     dispatch,
    301     subscribe,
    302     getState,
    303     replaceReducer,
    304     [$$observable]: observable
    305   }
    306 }
    3. combineReducers.js
      1 import ActionTypes from './utils/actionTypes'
      2 import warning from './utils/warning'
      3 import isPlainObject from './utils/isPlainObject'
      4 
      5 function getUndefinedStateErrorMessage(key, action) {
      6   const actionType = action && action.type
      7   const actionDescription =
      8     (actionType && `action "${String(actionType)}"`) || 'an action'
      9 
     10   return (
     11     `Given ${actionDescription}, reducer "${key}" returned undefined. ` +
     12     `To ignore an action, you must explicitly return the previous state. ` +
     13     `If you want this reducer to hold no value, you can return null instead of undefined.`
     14   )
     15 }
     16 
     17 function getUnexpectedStateShapeWarningMessage(
     18   inputState,
     19   reducers,
     20   action,
     21   unexpectedKeyCache
     22 ) {
     23   const reducerKeys = Object.keys(reducers)
     24   const argumentName =
     25     action && action.type === ActionTypes.INIT
     26       ? 'preloadedState argument passed to createStore'
     27       : 'previous state received by the reducer'
     28 
     29   if (reducerKeys.length === 0) {
     30     return (
     31       'Store does not have a valid reducer. Make sure the argument passed ' +
     32       'to combineReducers is an object whose values are reducers.'
     33     )
     34   }
     35 
     36   if (!isPlainObject(inputState)) {
     37     return (
     38       `The ${argumentName} has unexpected type of "` +
     39       {}.toString.call(inputState).match(/s([a-z|A-Z]+)/)[1] +
     40       `". Expected argument to be an object with the following ` +
     41       `keys: "${reducerKeys.join('", "')}"`
     42     )
     43   }
     44 
     45   const unexpectedKeys = Object.keys(inputState).filter(
     46     key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
     47   )
     48 
     49   unexpectedKeys.forEach(key => {
     50     unexpectedKeyCache[key] = true
     51   })
     52 
     53   if (action && action.type === ActionTypes.REPLACE) return
     54 
     55   if (unexpectedKeys.length > 0) {
     56     return (
     57       `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
     58       `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
     59       `Expected to find one of the known reducer keys instead: ` +
     60       `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
     61     )
     62   }
     63 }
     64 
     65 function assertReducerShape(reducers) {
     66   Object.keys(reducers).forEach(key => {
     67     const reducer = reducers[key]
     68     const initialState = reducer(undefined, { type: ActionTypes.INIT })
     69 
     70     if (typeof initialState === 'undefined') {
     71       throw new Error(
     72         `Reducer "${key}" returned undefined during initialization. ` +
     73           `If the state passed to the reducer is undefined, you must ` +
     74           `explicitly return the initial state. The initial state may ` +
     75           `not be undefined. If you don't want to set a value for this reducer, ` +
     76           `you can use null instead of undefined.`
     77       )
     78     }
     79 
     80     if (
     81       typeof reducer(undefined, {
     82         type: ActionTypes.PROBE_UNKNOWN_ACTION()
     83       }) === 'undefined'
     84     ) {
     85       throw new Error(
     86         `Reducer "${key}" returned undefined when probed with a random type. ` +
     87           `Don't try to handle ${
     88             ActionTypes.INIT
     89           } or other actions in "redux/*" ` +
     90           `namespace. They are considered private. Instead, you must return the ` +
     91           `current state for any unknown actions, unless it is undefined, ` +
     92           `in which case you must return the initial state, regardless of the ` +
     93           `action type. The initial state may not be undefined, but can be null.`
     94       )
     95     }
     96   })
     97 }
     98 
     99 /**
    100  * Turns an object whose values are different reducer functions, into a single
    101  * reducer function. It will call every child reducer, and gather their results
    102  * into a single state object, whose keys correspond to the keys of the passed
    103  * reducer functions.
    104  *
    105  * @param {Object} reducers An object whose values correspond to different
    106  * reducer functions that need to be combined into one. One handy way to obtain
    107  * it is to use ES6 `import * as reducers` syntax. The reducers may never return
    108  * undefined for any action. Instead, they should return their initial state
    109  * if the state passed to them was undefined, and the current state for any
    110  * unrecognized action.
    111  *
    112  * @returns {Function} A reducer function that invokes every reducer inside the
    113  * passed object, and builds a state object with the same shape.
    114  */
    115 
    116 /*
    117   combineReducers 顾名思义就是合并reduces的一个方法。
    118   1. 为了项目便于维护与管理我们就需要拆按模块拆分reducers。
    119   2. 而combineReducers就是为了解决这个的问题的。
    120 
    121  */
    122 export default function combineReducers(reducers) { // 参数reducers 是一个对象
    123   const reducerKeys = Object.keys(reducers) // 获取reducers的k
    124   const finalReducers = {}
    125   for (let i = 0; i < reducerKeys.length; i++) {
    126     const key = reducerKeys[i]
    127 
    128     if (process.env.NODE_ENV !== 'production') {
    129       if (typeof reducers[key] === 'undefined') {
    130         warning(`No reducer provided for key "${key}"`)
    131       }
    132     }
    133 
    134     // 深复制一份reducers出来, 防止后续操作出现不可控因素
    135     if (typeof reducers[key] === 'function') {
    136       finalReducers[key] = reducers[key]
    137     }
    138   }
    139   const finalReducerKeys = Object.keys(finalReducers)
    140 
    141   let unexpectedKeyCache
    142   if (process.env.NODE_ENV !== 'production') {
    143     unexpectedKeyCache = {}
    144   }
    145 
    146   let shapeAssertionError
    147   try {
    148     assertReducerShape(finalReducers)
    149   } catch (e) {
    150     shapeAssertionError = e
    151   }
    152   // 闭包的运用, 把合并的 reducer保存下来。
    153   return function combination(state = {}, action) {
    154     if (shapeAssertionError) {
    155       throw shapeAssertionError
    156     }
    157 
    158     if (process.env.NODE_ENV !== 'production') {
    159       const warningMessage = getUnexpectedStateShapeWarningMessage(
    160         state,
    161         finalReducers,
    162         action,
    163         unexpectedKeyCache
    164       )
    165       if (warningMessage) {
    166         warning(warningMessage)
    167       }
    168     }
    169 
    170     let hasChanged = false
    171     const nextState = {}
    172     for (let i = 0; i < finalReducerKeys.length; i++) {
    173       const key = finalReducerKeys[i]
    174       const reducer = finalReducers[key]
    175       const previousStateForKey = state[key]
    176       // 把合并的时候的key值作为Key值为标准。 在循环遍历的时候取出对应的 reducers 触发 reducer函数。
    177       /*
    178         其实对应的createStore.js中的
    179         try {
    180           isDispatching = true
    181           // 获取最新 的state 值。 currentState 经典之处闭包
    182           currentState = currentReducer(currentState, action)
    183         } finally {
    184           isDispatching = false
    185         }  
    186        */
    187       const nextStateForKey = reducer(previousStateForKey, action)
    188       if (typeof nextStateForKey === 'undefined') {
    189         const errorMessage = getUndefinedStateErrorMessage(key, action)
    190         throw new Error(errorMessage)
    191       }
    192       nextState[key] = nextStateForKey
    193       hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    194     }
    195     return hasChanged ? nextState : state
    196   }
    197 }
    198 /**
    199  * 使用方法
    200  * const reducers = combineReducers({ reducer1, reducer2 });
    201  * const store = createStore(reducers, preloadedState, enchancer);
    202  */
    4. bindActionCreators.js
     1 // 主要这个函数
     2 function bindActionCreator(actionCreator, dispatch) {
     3   return function() {
     4     return dispatch(actionCreator.apply(this, arguments))
     5   }
     6 }
     7 
     8 /**
     9  * Turns an object whose values are action creators, into an object with the
    10  * same keys, but with every function wrapped into a `dispatch` call so they
    11  * may be invoked directly. This is just a convenience method, as you can call
    12  * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
    13  *
    14  * For convenience, you can also pass a single function as the first argument,
    15  * and get a function in return.
    16  *
    17  * @param {Function|Object} actionCreators An object whose values are action
    18  * creator functions. One handy way to obtain it is to use ES6 `import * as`
    19  * syntax. You may also pass a single function.
    20  *
    21  * @param {Function} dispatch The `dispatch` function available on your Redux
    22  * store.
    23  *
    24  * @returns {Function|Object} The object mimicking the original object, but with
    25  * every action creator wrapped into the `dispatch` call. If you passed a
    26  * function as `actionCreators`, the return value will also be a single
    27  * function.
    28  */
    29 
    30 /*
    31    接受两个参数,一个action creator, 一个是 value的 action creator的对象。
    32    dispatch 。 一个由 Store 实列 提供的dispatch的函数。 看createStore.js源码就可以做知道其中原理。
    33  */
    34 export default function bindActionCreators(actionCreators, dispatch) {
    35   if (typeof actionCreators === 'function') {
    36     return bindActionCreator(actionCreators, dispatch)
    37   }
    38 
    39   if (typeof actionCreators !== 'object' || actionCreators === null) {
    40     throw new Error(
    41       `bindActionCreators expected an object or a function, instead received ${
    42         actionCreators === null ? 'null' : typeof actionCreators
    43       }. ` +
    44         `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    45     )
    46   }
    47 
    48   const keys = Object.keys(actionCreators)
    49   const boundActionCreators = {}
    50   for (let i = 0; i < keys.length; i++) {
    51     const key = keys[i]
    52     const actionCreator = actionCreators[key]
    53     if (typeof actionCreator === 'function') {
    54       boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    55     }
    56   }
    57   /*
    58      一个与原对象类似的对象,只不过这个对象的 value 都是会直接 dispatch 原 action creator 返回的结果的函数。
    59      如果传入一个单独的函数作为 actionCreators,那么返回的结果也是一个单独的函数。
    60      本来触发 action 的方法是 store.dispatch(action);
    61      经过这个方法封装后 可以直接调用函数名字
    62      aa('参数');
    63    */
    64   return boundActionCreators
    65 }

    5. applyMiddleware.js  在redux 中最难理解的一个函数。

    import compose from './compose'
    import createStore from "./createStore";
    
    /**
     * 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.
     */
    // 通过看源码知道 applyMiddleware返回一个高阶函数。
    /*
      applyMiddleware的使用地方
      const store = createStore(reducer,{}, applyMiddleware(...middlewares));
      
      由此可以看出 applyMiddlewares 的使用方法主要是和 createStore.js 中 createStore方法的第三个参数对应。翻开源码
      if (typeof enhancer !== 'undefined') {
        if (typeof enhancer !== 'function') {
          throw new Error('Expected the enhancer to be a function.')
        }
        // 上面两个判断是为了确保 enchancer是个函数
        return enhancer(createStore)(reducer, preloadedState)
      }
     */
    export default function applyMiddleware(...middlewares) {
      // createSotre 中 的第三个参数 enhancer
      return createStore => (...args) => {
        // 通过对应的代码可以发现其实 ...aregs 对应的是  reducer, preloadedState
        const store = createStore(...args)
        let dispatch = () => {
          throw new Error(
            `Dispatching while constructing your middleware is not allowed. ` +
              `Other middleware would not be applied to this dispatch.`
          )
        }
          // 定义中间件必须满足的条件。 API   getState, dispatch();
        const middlewareAPI = {
          getState: store.getState,
          dispatch: (...args) => dispatch(...args)  
        }
        const chain = middlewares.map(middleware => middleware(middlewareAPI))
    
          /**
           * 
    
          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)))
          }
           compose函数 主要是 利用数组 reducer 方法对参数的处理。
    
      */
          dispatch = compose(...chain)(store.dispatch)
        // 通过这个返回值我们可以知道 在createStore.js中enchancer的返回值。
        return {
          ...store,
          dispatch
        }
      }
    }
    
    /**
     *  applyMiddlewares函数比较难理解。 多看几个中间件,比如 logger 和 redux-thunk 等。对该方法能够更深的理解。
     */

    五、 redux的总结

    通过阅读redux的源码,印象最深的就是如何手动写个订阅模式,数据改变的时候,如何触发所有监听事件。闭包的运用登峰造极。其中最难的两个函数 applyMiddlewares 和compose.js 还需要细细体会。没有真正领悟到其精华之处。

    谢谢大家。

  • 相关阅读:
    easyui datagrid 遇到的坑 cannot read property ·· pageNum bug and so on
    原生js上传图片时的预览
    yield的理解
    js时间格式化
    js 实现复制粘贴时注意方法中需要两次点击实现的bug
    jquery 页面分页的实现
    easyui dialog 表单提交,弹框初始化赋值,dialog实现
    a标签(普通标签如span)没有disabled属性 ,怎样利用js实现该属性
    js之数据类型(对象类型——构造器对象——数组2)
    js之数据类型(对象类型——构造器对象——数组1)
  • 原文地址:https://www.cnblogs.com/createGod/p/9051166.html
Copyright © 2011-2022 走看看