Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Redux:百行代码千行文档 #1

Open
MrErHu opened this issue Jun 14, 2017 · 2 comments
Open

Redux:百行代码千行文档 #1

MrErHu opened this issue Jun 14, 2017 · 2 comments
Labels

Comments

@MrErHu
Copy link
Owner

MrErHu commented Jun 14, 2017

  接触Redux不过短短半年,从开始看官方文档的一头雾水,到渐渐已经理解了Redux到底是在做什么,但是绝大数场景下Redux都是配合React一同使用的,因而会引入了React-Redux库,但是正是因为React-Redux库封装了大量方法,使得我们对Redux的理解变的开始模糊。这篇文章将会在Redux源码的角度分析Redux,希望你在阅读之前有部分Redux的基础。

  上图是Redux的流程图,具体的不做介绍,不了解的同学可以查阅一下Redux的官方文档。写的非常详细。下面的代码结构为Redux的master分支:

├── applyMiddleware.js
├── bindActionCreators.js
├── combineReducers.js
├── compose.js
├── createStore.js
├── index.js
└── utils
└── warning.js

Redux中src文件夹下目录如上所示,文件名基本就是对应我们所熟悉的Redux的API,首先看一下index.js中的代码:

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

  上面的代码非常的简单了,只不过是把所有的方法对外导出。其中isCrushed是用来检查函数名是否已经被压缩(minification)。如果函数当前不是在生产环境中并且函数名被压缩了,就提示用户。process是Node 应用自带的一个全局变量,可以获取当前进程的若干信息。在许多前端库中,经常会使用 process.env.NODE_ENV这个环境变量来判断当前是在开发环境还是生产环境中。这个小例子我们可以get到一个hack的方法,如果判断一个js函数名时候被压缩呢?我们可以先预定义一个虚函数(虽然JavaScript中没有虚函数一说,这里的虚函数(dummy function)指代的是没有函数体的函数),然后判断执行时的函数名是否和预定义的一样,就像上面的代码:

function isCrushed() {}
if(typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed'){
  //has minified
}

compose

   从易到难,我们在看一个稍微简单的对外方法compose

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

   理解这个函数之前我们首先看一下reduce方法,这个方法我是看了好多遍现在仍然是印象模糊,虽然之前介绍过reduce,但是还是再次回忆一下Array.prototye.reduce:

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.

   reduce()函数对一个累加值和数组中的每一个元素(从左到右)应用一个函数,将其reduce成一个单值,例如:

var sum = [0, 1, 2, 3].reduce(function(acc, val) {
  return acc + val;
}, 0);
// sum is 6

   reduce()函数接受两个参数:一个回调函数和初始值,回调函数会被从左到右应用到数组的每一个元素,其中回调函数的定义是

/**
 * accumulator: 累加器累加回调的值,它是上一次调用回调时返回的累积值或者是初始值
 * currentValue: 当前数组遍历的值
 * currenIndex: 当前元素的索引值
 * array: 整个数组
 */
function (accumulator,currentValue,currentIndex,array){
  
}

  现在回头看看compose函数都在做什么,compose函数从左到右组合(compose)多个单参函数。最右边的函数可以按照定义接受多个参数,如果compose的参数为空,则返回一个空函数。如果参数长度为1,则返回函数本身。如果函数的参数为数组,这时候我们返回

  return funcs.reduce((a, b) => (...args) => a(b(...args)))

  我们知道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的实现如下:

export default function compose(...funcs) {
     return funcs.reduceRight((composed, f) => f(composed));
}

  这样看起来是不是更容易理解compose函数的功能。

bindActionCreators

  bindActionCreators也是Redux中非常常见的API,主要实现的就是将ActionCreatordispatch进行绑定,看一下官方的解释:

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.

  翻译过来就是bindActionCreators将值为actionCreator的对象转化成具有相同键值的对象,但是每一个actionCreator都会被dispatch所包裹调用,因此可以直接使用。话不多说,来看看它是怎么实现的:

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
}

  对于处理单个actionCreator的方法是

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

  代码也是非常的简单,无非是返回一个新的函数,该函数调用时会将actionCreator返回的纯对象进行dispatch。而对于函数bindActionCreators首先会判断actionCreators是不是函数,如果是函数就直接调用bindActionCreator。当actionCreators不是对象时会抛出错误。接下来:

  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

  这段代码也是非常简单,甚至我觉得我都能写出来,无非就是对对象actionCreators中的所有值调用bindActionCreator,然后返回新的对象。恭喜你,又解锁了一个文件~

applyMiddleware

  applyMiddleware是Redux Middleware的一个重要API,这个部分代码已经不需要再次解释了,没有看过的同学戳这里Redux:Middleware你咋就这么难,里面有详细的介绍。

createStore

  createStore作为Redux的核心API,其作用就是生成一个应用唯一的store。其函数的签名为:

function createStore(reducer, preloadedState, enhancer) {}

  前两个参数非常熟悉,reducer是处理的reducer纯函数,preloadedState是初始状态,而enhancer使用相对较少,enhancer是一个高阶函数,用来对原始的createStore的功能进行增强。具体我们可以看一下源码:

具体代码如下:

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

我们来逐步解读一下:

  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  我们发现如果没有传入参数enhancer,并且preloadedState的值又是一个函数的话,createStore会认为你省略了preloadedState,因此第二个参数就是enhancer

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

  如果你传入了enhancer但是却又不是函数类型。会抛出错误。如果传入的reducer也不是函数,抛出相关错误。接下来才是createStore重点,初始化:


  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  currentReducer是用来存储当前的reducer函数。currentState用来存储当前store中的数据,初始化为默认的preloadedState,currentListeners用来存储当前的监听者。而isDispatching用来当前是否属于正在处理dispatch的阶段。然后函数声明了一系列函数,最后返回了:

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

  显然可以看出来返回来的函数就是store。比如我们可以调用store.dispatch。让我们依次看看各个函数在做什么。

dispatch

  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
  }

  我们看看dispath做了什么,首先检查传入的action是不是纯对象,如果不是则抛出异常。然后检测,action中是否存在type,不存在也给出相应的错误提示。然后判断isDispatching是否为true,主要是预防的是在reducer中做dispatch操作,如果在reduder中做了dispatch,而dispatch又必然会导致reducer的调用,就会造成死循环。然后我们将isDispatching置为true,调用当前的reducer函数,并且返回新的state存入currentState,并将isDispatching置回去。最后依次调用监听者store已经发生了变化,但是我们并没有将新的store作为参数传递给监听者,因为我们知道监听者函数内部可以通过调用唯一获取store的函数store.getState()获取最新的store

getState

  function getState() {
    return currentState
  }

  实在太简单了,自行体会。

replaceReducer

  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
  }

  replaceReducer的使用相对也是非常少的,主要用户热更新reducer

subscribe

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

  subscribe用来订阅store变化的函数。首先判断传入的listener是否是函数。然后又调用了ensureCanMutateNextListeners,

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  可以看到ensureCanMutateNextListeners用来判断nextListenerscurrentListeners是否是完全相同,如果相同(===),将nextListeners赋值为currentListeners的拷贝(值相同,但不是同一个数组),然后将当前的监听函数传入nextListeners。最后返回一个unsubscribe函数用来移除当前监听者函数。需要注意的是,isSubscribed是以闭包的形式判断当前监听者函数是否在监听,从而保证只有第一次调用unsubscribe才是有效的。但是为什么会存在nextListeners呢?

  首先可以在任何时间点添加listener。无论是dispatchaction时,还是state值正在发生改变的时候。但是需要注意的,在每一次调用dispatch之前,订阅者仅仅只是一份快照(snapshot),如果是在listeners被调用期间发生订阅(subscribe)或者解除订阅(unsubscribe),在本次通知中并不会立即生效,而是在下次中生效。因此添加的过程是在nextListeners中添加的订阅者,而不是直接添加到currentListeners。然后在每一次调用dispatch的时候都会做:

const listeners = currentListeners = nextListeners

来同步currentListenersnextListeners

observable

  该部分不属于本次文章讲解到的内容,主要涉及到RxJS和响应异步Action。以后有机会(主要是我自己搞明白了),会单独讲解。

combineReducers

  combineReducers的主要作用就是将大的reducer函数拆分成一个个小的reducer分别处理,看一下它是如何实现的:

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

  首先,通过一个for循环去遍历参数reducers,将对应值为函数的属性赋值到finalReducers。然后声明变量unexpectedKeyCache,如果在非生产环境,会将其初始化为{}。然后执行assertReducerShape(finalReducers),如果抛出异常会将错误信息存储在shapeAssertionError。我们看一下shapeAssertionError在做什么?

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

可以看出assertReducerShape函数的主要作用就是判断reducers中的每一个reduceraction{ type: ActionTypes.INIT }时是否有初始值,如果没有则会抛出异常。并且会对reduer执行一次随机的action,如果没有返回,则抛出错误,告知你不要处理redux中的私有的action,对于未知的action应当返回当前的stat。并且初始值不能为undefined但是可以是null

  接着我们看到combineReducers返回了一个combineReducers函数:

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
}

combination函数中我们首先对shapeAssertionError中可能存在的异常进行处理。接着,如果是在开发环境下,会执行getUnexpectedStateShapeWarningMessage,看看getUnexpectedStateShapeWarningMessage是如何定义的:

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

  我们简要地看看getUnexpectedStateShapeWarningMessage处理了哪几种问题:

  1. reducer中是不是存在reducer
  2. state是否是纯Object对象
  3. state中存在reducer没有处理的项,但是仅会在第一次提醒,之后就忽略了。

然后combination执行其核心部分代码:

    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

  使用变量nextState记录本次执行reducer返回的state。hasChanged用来记录前后state是否发生改变。循环遍历reducers,将对应的store的部分交给相关的reducer处理,当然对应各个reducer返回的新的state仍然不可以是undefined。最后根据hasChanged是否改变来决定返回nextState还是state,这样就保证了在不变的情况下仍然返回的是同一个对象。

  最后,其实我们发现Redux的源码非常的精炼,也并不复杂,但是Dan Abramov能从Flux的思想演变到现在的Redux思想也是非常不易,希望此篇文章使得你对Redux有更深的理解。

@unknownzjc
Copy link

unknownzjc commented Aug 6, 2018

我看到combineReducers这个方法里面,做了两次那reducers的key的操作,还有把reducer放到函数里面的一个变量里面去。其实为什么不直接就遍历reducers就行呢,照理应该也不会有改变值的操作什么的,是不是因为assertReducerShape这个方法的原因呢

@MrErHu
Copy link
Owner Author

MrErHu commented Oct 23, 2018

@Taikyo 你说应该就是在一次循环既赋值又判断assertReducerShape的逻辑吧。
想来是可以一次处理的,我猜测(估计)作者还是想把两个无关的逻辑分离吧。

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

No branches or pull requests

2 participants