Redux: 百行代码千行文档

pbmz5292 2年前
   <p>接触Redux不过短短半年,从开始看官方文档的一头雾水,到渐渐已经理解了Redux到底是在做什么,但是绝大数场景下Redux都是配合React一同使用的,因而会引入了React-Redux库,但是正是因为React-Redux库封装了大量方法,使得我们对Redux的理解变的开始模糊。这篇文章将会在Redux源码的角度分析Redux,希望你在阅读之前有部分Redux的基础。</p>    <p><img src="https://simg.open-open.com/show/bdded94b95847886874f54f90b3184c8.jpg"></p>    <p>上图是Redux的流程图,具体的不做介绍,不了解的同学可以查阅一下Redux的官方文档。写的非常详细。下面的代码结构为Redux的master分支:</p>    <p>├── applyMiddleware.js</p>    <p>├── bindActionCreators.js</p>    <p>├── combineReducers.js</p>    <p>├── compose.js</p>    <p>├── createStore.js</p>    <p>├── index.js</p>    <p>└── utils</p>    <p>└── warning.js</p>    <p>Redux中src文件夹下目录如上所示,文件名基本就是对应我们所熟悉的Redux的API,首先看一下index.js中的代码:</p>    <pre>  <code class="language-javascript">/*  * 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 DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' +      'to ensure you have the correct code for your production build.'    )  }    export {    createStore,    combineReducers,    bindActionCreators,    applyMiddleware,    compose  }</code></pre>    <p>上面的代码非常的简单了,只不过是把所有的方法对外导出。其中 isCrushed 是用来检查函数名是否已经被压缩(minification)。如果函数当前不是在生产环境中并且函数名被压缩了,就提示用户。process是Node 应用自带的一个全局变量,可以获取当前进程的若干信息。在许多前端库中,经常会使用 process.env.NODE_ENV这个环境变量来判断当前是在开发环境还是生产环境中。这个小例子我们可以get到一个hack的方法,如果判断一个js函数名时候被压缩呢?我们可以先预定义一个虚函数(虽然JavaScript中没有虚函数一说,这里的虚函数(dummy function)指代的是没有函数体的函数),然后判断执行时的函数名是否和预定义的一样,就像上面的代码:</p>    <pre>  <code class="language-javascript">function isCrushed() {}  if(typeof isCrushed.name === 'string' && isCrushed.name === 'isCrushed'){      //has minified  }</code></pre>    <h2>compose</h2>    <p>从易到难,我们在看一个稍微简单的对外方法 compose</p>    <pre>  <code class="language-javascript">/**   * 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]    }      return funcs.reduce((a, b) => (...args) => a(b(...args)))  }</code></pre>    <p>理解这个函数之前我们首先看一下 reduce 方法,这个方法我是看了好多遍现在仍然是印象模糊,虽然之前介绍过 reduce ,但是还是再次回忆一下 Array.prototye.reduce :</p>    <p>The reduce() method applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.</p>    <p>reduce() 函数对一个累加值和数组中的每一个元素(从左到右)应用一个函数,将其 reduce 成一个单值,例如:</p>    <pre>  <code class="language-javascript">var sum = [0, 1, 2, 3].reduce(function(acc, val) {    return acc + val;  }, 0);  // sum is 6</code></pre>    <p>reduce() 函数接受两个参数:一个回调函数和初始值,回调函数会被从左到右应用到数组的每一个元素,其中回调函数的定义是</p>    <pre>  <code class="language-javascript">/**   * accumulator: 累加器累加回调的值,它是上一次调用回调时返回的累积值或者是初始值   * currentValue: 当前数组遍历的值   * currenIndex: 当前元素的索引值   * array: 整个数组   */  function (accumulator,currentValue,currentIndex,array){    }</code></pre>    <p>现在回头看看 compose 函数都在做什么, compose 函数从左到右组合(compose)多个单参函数。最右边的函数可以按照定义接受多个参数,如果 compose 的参数为空,则返回一个空函数。如果参数长度为1,则返回函数本身。如果函数的参数为数组,这时候我们返回</p>    <pre>  <code class="language-javascript">return funcs.reduce((a, b) => (...args) => a(b(...args)))</code></pre>    <p>我们知道 reduce 函数返回是一个值。上面函数传入的回调函数是 (a, b) => (...args) => a(b(...args)) 其中 a 是当前的累积值, b 是数组中当前遍历的值。假设调用函数的方式是 compose(f,g,h) ,首先第一次执行回调函数时, a 的实参是函数 f , b 的实参是 g ,第二次调用的是, a 的实参是 (...args) => f(g(...args)) , b 的实参是 h ,最后函数返回的是 (...args) =>x(h(...args)) ,其中x为 (...args) => f(g(...args)) ,所以我们最后可以推导出运行 compose(f,g,h) 的结果是 (...args) => f(g(h(...args))) 。发现了没有,这里其实通过 reduce 实现了 reduceRight 的从右到左遍历的功能,但是却使得代码相对较难理解。在Redux 1.0.1版本中 compose 的实现如下:</p>    <pre>  <code class="language-javascript">export default function compose(...funcs) {       return funcs.reduceRight((composed, f) => f(composed));  }</code></pre>    <p>这样看起来是不是更容易理解 compose 函数的功能。</p>    <h2>bindActionCreators</h2>    <p>bindActionCreators 也是Redux中非常常见的API,主要实现的就是将 ActionCreator 与 dispatch 进行绑定,看一下官方的解释:</p>    <p>Turns an object whose values are action creators, into an object with the same keys, but with every action creator wrapped into a dispatch call so they may be invoked directly.</p>    <p>翻译过来就是 bindActionCreators 将值为 actionCreator 的对象转化成具有相同键值的对象,但是每一个 actionCreator 都会被 dispatch 所包裹调用,因此可以直接使用。话不多说,来看看它是怎么实现的:</p>    <pre>  <code class="language-javascript">import warning from './utils/warning'    function bindActionCreator(actionCreator, dispatch) {    return (...args) => dispatch(actionCreator(...args))  }    export default function bindActionCreators(actionCreators, dispatch) {    if (typeof actionCreators === 'function') {      return bindActionCreator(actionCreators, dispatch)    }      if (typeof actionCreators !== 'object' || actionCreators === null) {      throw new Error(        `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +        `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`      )    }      const keys = Object.keys(actionCreators)    const boundActionCreators = {}    for (let i = 0; i < keys.length; i++) {      const key = keys[i]      const actionCreator = actionCreators[key]      if (typeof actionCreator === 'function') {        boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)      } else {        warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'.`)      }    }    return boundActionCreators  }</code></pre>    <p>对于处理单个 actionCreator 的方法是</p>    <pre>  <code class="language-javascript">function bindActionCreator(actionCreator, dispatch) {    return (...args) => dispatch(actionCreator(...args))  }</code></pre>    <p>代码也是非常的简单,无非是返回一个新的函数,该函数调用时会将 actionCreator 返回的纯对象进行 dispatch 。而对于函数 bindActionCreators 首先会判断 actionCreators 是不是函数,如果是函数就直接调用 bindActionCreator 。当 actionCreators 不是对象时会抛出错误。接下来:</p>    <pre>  <code class="language-javascript">const keys = Object.keys(actionCreators)    const boundActionCreators = {}    for (let i = 0; i < keys.length; i++) {      const key = keys[i]      const actionCreator = actionCreators[key]      if (typeof actionCreator === 'function') {        boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)      } else {        warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'.`)      }    }    return boundActionCreators</code></pre>    <p>这段代码也是非常简单,甚至我觉得我都能写出来,无非就是对对象 actionCreators 中的所有值调用 bindActionCreator ,然后返回新的对象。恭喜你,又解锁了一个文件~</p>    <h3>applyMiddleware</h3>    <p>applyMiddleware 是Redux Middleware的一个重要API,这个部分代码已经不需要再次解释了,没有看过的同学戳这里 <a href="/misc/goto?guid=4959749509106774335" rel="nofollow,noindex">Redux:Middleware你咋就这么难</a> ,里面有详细的介绍。</p>    <h3>createStore</h3>    <p>createStore 作为Redux的核心API,其作用就是生成一个应用唯一的store。其函数的签名为:</p>    <pre>  <code class="language-javascript">function createStore(reducer, preloadedState, enhancer) {}</code></pre>    <p>前两个参数非常熟悉, reducer 是处理的 reducer 纯函数, preloadedState 是初始状态,而 enhancer 使用相对较少, enhancer 是一个高阶函数,用来对原始的 createStore 的功能进行增强。具体我们可以看一下源码:</p>    <p>具体代码如下:</p>    <pre>  <code class="language-javascript">import isPlainObject from 'lodash/isPlainObject'  import $$observable from 'symbol-observable'    export const ActionTypes = {    INIT: '@@redux/INIT'  }    export default function createStore(reducer, preloadedState, enhancer) {    if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {      enhancer = preloadedState      preloadedState = undefined    }      if (typeof enhancer !== 'undefined') {      if (typeof enhancer !== 'function') {        throw new Error('Expected the enhancer to be a function.')      }        return enhancer(createStore)(reducer, preloadedState)    }      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    let isDispatching = false      function ensureCanMutateNextListeners() {      if (nextListeners === currentListeners) {        nextListeners = currentListeners.slice()      }    }        function getState() {      return currentState    }      function subscribe(listener) {      if (typeof listener !== 'function') {        throw new Error('Expected listener to be a function.')      }        let isSubscribed = true        ensureCanMutateNextListeners()      nextListeners.push(listener)        return function unsubscribe() {        if (!isSubscribed) {          return        }          isSubscribed = false          ensureCanMutateNextListeners()        const index = nextListeners.indexOf(listener)        nextListeners.splice(index, 1)      }    }      function dispatch(action) {      if (!isPlainObject(action)) {        throw new Error(          'Actions must be plain objects. ' +          'Use custom middleware for async actions.'        )      }        if (typeof action.type === 'undefined') {        throw new Error(          'Actions may not have an undefined "type" property. ' +          'Have you misspelled a constant?'        )      }        if (isDispatching) {        throw new Error('Reducers may not dispatch actions.')      }        try {        isDispatching = true        currentState = currentReducer(currentState, action)      } finally {        isDispatching = false      }        const listeners = currentListeners = nextListeners      for (let i = 0; i < listeners.length; i++) {        const listener = listeners[i]        listener()      }        return action    }      function replaceReducer(nextReducer) {      if (typeof nextReducer !== 'function') {        throw new Error('Expected the nextReducer to be a function.')      }        currentReducer = nextReducer      dispatch({ type: ActionTypes.INIT })    }      function observable() {      const outerSubscribe = subscribe      return {        subscribe(observer) {          if (typeof observer !== 'object') {            throw new TypeError('Expected the observer to be an object.')          }            function observeState() {            if (observer.next) {              observer.next(getState())            }          }            observeState()          const unsubscribe = outerSubscribe(observeState)          return { unsubscribe }        },          [$$observable]() {          return this        }      }    }      dispatch({ type: ActionTypes.INIT })      return {      dispatch,      subscribe,      getState,      replaceReducer,      [$$observable]: observable    }  }</code></pre>    <p>我们来逐步解读一下:</p>    <pre>  <code class="language-javascript">if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {      enhancer = preloadedState      preloadedState = undefined    }</code></pre>    <p>我们发现如果没有传入参数 enhancer ,并且 preloadedState 的值又是一个函数的话, createStore 会认为你省略了 preloadedState ,因此第二个参数就是 enhancer 。</p>    <pre>  <code class="language-javascript">if (typeof enhancer !== 'undefined') {      if (typeof enhancer !== 'function') {        throw new Error('Expected the enhancer to be a function.')      }        return enhancer(createStore)(reducer, preloadedState)    }      if (typeof reducer !== 'function') {      throw new Error('Expected the reducer to be a function.')    }</code></pre>    <p>如果你传入了 enhancer 但是却又不是函数类型。会抛出错误。如果传入的 reducer 也不是函数,抛出相关错误。接下来才是 createStore 重点,初始化:</p>    <pre>  <code class="language-javascript">let currentReducer = reducer    let currentState = preloadedState    let currentListeners = []    let nextListeners = currentListeners    let isDispatching = false</code></pre>    <p>currentReducer 是用来存储当前的 reducer 函数。 currentState 用来存储当前store中的数据,初始化为默认的 preloadedState , currentListeners 用来存储当前的监听者。而 isDispatching 用来当前是否属于正在处理 dispatch 的阶段。然后函数声明了一系列函数,最后返回了:</p>    <pre>  <code class="language-javascript">{      dispatch,      subscribe,      getState,      replaceReducer,      [$$observable]: observable  }</code></pre>    <p>显然可以看出来返回来的函数就是 store 。比如我们可以调用 store.dispatch 。让我们依次看看各个函数在做什么。</p>    <h3>dispatch</h3>    <pre>  <code class="language-javascript">function dispatch(action) {      if (!isPlainObject(action)) {        throw new Error(          'Actions must be plain objects. ' +          'Use custom middleware for async actions.'        )      }        if (typeof action.type === 'undefined') {        throw new Error(          'Actions may not have an undefined "type" property. ' +          'Have you misspelled a constant?'        )      }        if (isDispatching) {        throw new Error('Reducers may not dispatch actions.')      }        try {        isDispatching = true        currentState = currentReducer(currentState, action)      } finally {        isDispatching = false      }        const listeners = currentListeners = nextListeners        for (let i = 0; i < listeners.length; i++) {        const listener = listeners[i]        listener()      }        return action    }</code></pre>    <p>我们看看 dispath 做了什么,首先检查传入的 action 是不是纯对象,如果不是则抛出异常。然后检测, action 中是否存在 type ,不存在也给出相应的错误提示。然后判断 isDispatching 是否为 true ,主要是预防的是在 reducer 中做 dispatch 操作,如果在 reduder 中做了 dispatch ,而 dispatch 又必然会导致 reducer 的调用,就会造成死循环。然后我们将 isDispatching 置为 true ,调用当前的 reducer 函数,并且返回新的 state 存入 currentState ,并将 isDispatching 置回去。最后依次调用监听者 store 已经发生了变化,但是我们并没有将新的 store 作为参数传递给监听者,因为我们知道监听者函数内部可以通过调用唯一获取 store 的函数 store.getState() 获取最新的 store 。</p>    <h3>getState</h3>    <pre>  <code class="language-javascript">function getState() {      return currentState    }</code></pre>    <p>实在太简单了,自行体会。</p>    <h3>replaceReducer</h3>    <pre>  <code class="language-javascript">function replaceReducer(nextReducer) {      if (typeof nextReducer !== 'function') {        throw new Error('Expected the nextReducer to be a function.')      }        currentReducer = nextReducer      dispatch({ type: ActionTypes.INIT })    }</code></pre>    <p>replaceReducer 的使用相对也是非常少的,主要用户热更新 reducer 。</p>    <h3>subscribe</h3>    <pre>  <code class="language-javascript">function subscribe(listener) {      if (typeof listener !== 'function') {        throw new Error('Expected listener to be a function.')      }        let isSubscribed = true        ensureCanMutateNextListeners()      nextListeners.push(listener)        return function unsubscribe() {        if (!isSubscribed) {          return        }          isSubscribed = false          ensureCanMutateNextListeners()        const index = nextListeners.indexOf(listener)        nextListeners.splice(index, 1)      }    }</code></pre>    <p>subscribe 用来订阅 store 变化的函数。首先判断传入的 listener 是否是函数。然后又调用了 ensureCanMutateNextListeners ,</p>    <pre>  <code class="language-javascript">function ensureCanMutateNextListeners() {      if (nextListeners === currentListeners) {        nextListeners = currentListeners.slice()      }    }</code></pre>    <p>可以看到 ensureCanMutateNextListeners 用来判断 nextListeners 和 currentListeners 是否是完全相同,如果不相同,将 nextListeners 赋值为 nextListeners (值相同,但不是同一个数组),然后将当前的监听函数传入 nextListeners 。最后返回一个 unsubscribe 函数用来移除当前监听者函数。需要注意的是, isSubscribed 是以闭包的形式判断当前监听者函数是否在监听,从而保证只有第一次调用 unsubscribe 才是有效的。但是为什么会存在 nextListeners 呢?</p>    <p>首先可以在任何时间点添加 listener 。无论是 dispatch action时,还是 state 值正在发生改变的时候。但是需要注意的,在每一次调用 dispatch 之前,订阅者仅仅只是一份快照(snapshot),如果是在 listeners 被调用期间发生订阅(subscribe)或者解除订阅(unsubscribe),在本次通知中并不会立即生效,而是在下次中生效。因此添加的过程是在 nextListeners 中添加的订阅者,而不是直接添加到 currentListeners 。然后在每一次调用 dispatch 的时候都会做:</p>    <pre>  <code class="language-javascript">const listeners = currentListeners = nextListeners</code></pre>    <p>来同步 currentListeners 和 nextListeners 。</p>    <h3>observable</h3>    <p>该部分不属于本次文章讲解到的内容,主要涉及到RxJS和响应异步Action。以后有机会(主要是我自己搞明白了),会单独讲解。</p>    <h2>combineReducers</h2>    <p>combineReducers 的主要作用就是将大的 reducer 函数拆分成一个个小的 reducer 分别处理,看一下它是如何实现的:</p>    <pre>  <code class="language-javascript">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') {          warning(`No reducer provided for key "${key}"`)        }      }        if (typeof reducers[key] === 'function') {        finalReducers[key] = reducers[key]      }    }    const finalReducerKeys = Object.keys(finalReducers)      let unexpectedKeyCache    if (process.env.NODE_ENV !== 'production') {      unexpectedKeyCache = {}    }      let shapeAssertionError    try {      assertReducerShape(finalReducers)    } catch (e) {      shapeAssertionError = e    }      return function combination(state = {}, action) {      if (shapeAssertionError) {        throw shapeAssertionError      }        if (process.env.NODE_ENV !== 'production') {        const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)        if (warningMessage) {          warning(warningMessage)        }      }        let hasChanged = false      const nextState = {}      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    }  }</code></pre>    <p>首先,通过一个 for 循环去遍历参数 reducers ,将对应值为函数的属性赋值到 finalReducers 。然后声明变量 unexpectedKeyCache ,如果在非生产环境,会将其初始化为 {} 。然后执行 assertReducerShape(finalReducers) ,如果抛出异常会将错误信息存储在 shapeAssertionError 。我们看一下 shapeAssertionError 在做什么?</p>    <pre>  <code class="language-javascript">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.`        )      }        const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')      if (typeof reducer(undefined, { type }) === '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.`        )      }    })  }</code></pre>    <p>可以看出 assertReducerShape 函数的主要作用就是判断 reducers 中的每一个 reducer 在 action 为 { type: ActionTypes.INIT } 时是否有初始值,如果没有则会抛出异常。并且会对 reduer 执行一次随机的 action ,如果没有返回,则抛出错误,告知你不要处理redux中的私有的action,对于未知的action应当返回当前的stat。并且初始值不能为 undefined 但是可以是 null 。</p>    <p>接着我们看到 combineReducers 返回了一个 combineReducers 函数:</p>    <pre>  <code class="language-javascript">return function combination(state = {}, action) {      if (shapeAssertionError) {        throw shapeAssertionError      }        if (process.env.NODE_ENV !== 'production') {        const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)        if (warningMessage) {          warning(warningMessage)        }      }        let hasChanged = false      const nextState = {}      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  }</code></pre>    <p>在 combination 函数中我们首先对 shapeAssertionError 中可能存在的异常进行处理。接着,如果是在开发环境下,会执行 getUnexpectedStateShapeWarningMessage ,看看 getUnexpectedStateShapeWarningMessage 是如何定义的:</p>    <pre>  <code class="language-javascript">function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {    const reducerKeys = Object.keys(reducers)    const argumentName = action && action.type === ActionTypes.INIT ?      'preloadedState argument passed to createStore' :      'previous state received by the reducer'      if (reducerKeys.length === 0) {      return (        'Store does not have a valid reducer. Make sure the argument passed ' +        'to combineReducers is an object whose values are reducers.'      )    }      if (!isPlainObject(inputState)) {      return (        `The ${argumentName} has unexpected type of "` +        ({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +        `". Expected argument to be an object with the following ` +        `keys: "${reducerKeys.join('", "')}"`      )    }      const unexpectedKeys = Object.keys(inputState).filter(key =>      !reducers.hasOwnProperty(key) &&      !unexpectedKeyCache[key]    )      unexpectedKeys.forEach(key => {      unexpectedKeyCache[key] = true    })      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.`      )    }  }</code></pre>    <p>我们简要地看看 getUnexpectedStateShapeWarningMessage 处理了哪几种问题:</p>    <ol>     <li>reducer中是不是存在reducer</li>     <li>state是否是纯Object对象</li>     <li>state中存在reducer没有处理的项,但是仅会在第一次提醒,之后就忽略了。</li>    </ol>    <p>然后 combination 执行其核心部分代码:</p>    <pre>  <code class="language-javascript">let hasChanged = false      const nextState = {}      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</code></pre>    <p>使用变量 nextState 记录本次执行 reducer 返回的state。 hasChanged 用来记录前后 state 是否发生改变。循环遍历 reducers ,将对应的 store 的部分交给相关的 reducer 处理,当然对应各个 reducer 返回的新的 state 仍然不可以是 undefined 。最后根据 hasChanged 是否改变来决定返回 nextState 还是 state ,这样就保证了在不变的情况下仍然返回的是同一个对象。</p>    <p>最后,其实我们发现Redux的源码非常的精炼,也并不复杂,但是Dan Abramov能从Flux的思想演变到现在的Redux思想也是非常不易,希望此篇文章使得你对Redux有更深的理解。</p>    <p> </p>    <p>来自:https://juejin.im/post/5930a1bdfe88c2006198bdf0</p>    <p> </p>