Redux源码

在进入正题前,我们首先来看一下在项目中是如何使用 Redux 的,根据使用步骤来讲解源码。

// 首先把多个 reducer 通过 combineReducers 组合在一起
const appReducer = combineReducers({
    user: UserReducer,
    goods: GoodsReducer,
    order: OrdersReducer,
    chat: ChatReducer
});
// 然后将 appReducer 传入 createStore,并且通过 applyMiddleware 使用了中间件 thunkMiddleware
// 然后在需要的地方发起 dispatch(action) 引起 state 改变
export default function configureStore() {
    const store = createStore(
        rootReducer,
        compose(
            applyMiddleware(thunkMiddleware),
            window.devToolsExtension ? window.devToolsExtension() : f => f
        )
    );
    return store;
}

介绍完了使用步骤,接下来进入正题。

源码解析

首先让我们来看下 combineReducers 函数

// 传入一个 object
export default function combineReducers(reducers) {
 // 获取该 Object 的 key 值
    const reducerKeys = Object.keys(reducers)
    // 过滤后的 reducers
    const finalReducers = {}
    // 获取每一个 key 对应的 value
    // 在开发环境下判断值是否为 undefined
    // 然后将值类型是函数的值放入 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]
        }
    }
    // 拿到过滤后的 reducers 的 key 值
    const finalReducerKeys = Object.keys(finalReducers)

    // 在开发环境下判断,保存不期望 key 的缓存用以下面做警告
    let unexpectedKeyCache
    if (process.env.NODE_ENV !== 'production') {
        unexpectedKeyCache = {}
    }

    let shapeAssertionError
    try {
    // 该函数解析在下面
        assertReducerShape(finalReducers)
    } catch (e) {
        shapeAssertionError = e
    }
// combineReducers 函数返回一个函数,也就是合并后的 reducer 函数
// 该函数返回总的 state
// 并且你也可以发现这里使用了闭包,函数里面使用到了外面的一些属性
    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)
            }
        }
        // state 是否改变
        let hasChanged = false
        // 改变后的 state
        const nextState = {}
        for (let i = 0; i < finalReducerKeys.length; i++) {
        // 拿到相应的 key
            const key = finalReducerKeys[i]
            // 获得 key 对应的 reducer 函数
            const reducer = finalReducers[key]
            // state 树下的 key 是与 finalReducers 下的 key 相同的
            // 所以你在 combineReducers 中传入的参数的 key 即代表了 各个 reducer 也代表了各个 state
            const previousStateForKey = state[key]
            // 然后执行 reducer 函数获得该 key 值对应的 state
            const nextStateForKey = reducer(previousStateForKey, action)
            // 判断 state 的值,undefined 的话就报错
            if (typeof nextStateForKey === 'undefined') {
                const errorMessage = getUndefinedStateErrorMessage(key, action)
                throw new Error(errorMessage)
            }
            // 然后将 value 塞进去
            nextState[key] = nextStateForKey
            // 如果 state 改变
            hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        // state 只要改变过,就返回新的 state
        return hasChanged ? nextState : state
    }
}

combineReducers 函数总的来说很简单,总结来说就是接收一个对象,将参数过滤后返回一个函数。该函数里有一个过滤参数后的对象 finalReducers,遍历该对象,然后执行对象中的每一个 reducer 函数,最后将新的 state 返回。

接下来让我们来看看 combinrReducers 中用到的两个函数

// 这是执行的第一个用于抛错的函数
function assertReducerShape(reducers) {
// 将 combineReducers 中的参数遍历
    Object.keys(reducers).forEach(key => {
        const reducer = reducers[key]
        // 给他传入一个 action
        const initialState = reducer(undefined, { type: ActionTypes.INIT })
        // 如果得到的 state 为 undefined 就抛错
        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.`
            )
        }
        // 再过滤一次,考虑到万一你在 reducer 中给 ActionTypes.INIT 返回了值
        // 传入一个随机的 action 判断值是否为 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.`
            )
        }
    })
}

function getUnexpectedStateShapeWarningMessage(
    inputState,
    reducers,
    action,
    unexpectedKeyCache
) {
    // 这里的 reducers 已经是 finalReducers
    const reducerKeys = Object.keys(reducers)
    const argumentName =
        action && action.type === ActionTypes.INIT
            ? 'preloadedState argument passed to createStore'
            : 'previous state received by the reducer'

    // 如果 finalReducers 为空
    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.'
        )
    }
        // 如果你传入的 state 不是对象
    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('", "')}"`
        )
    }
        // 将参入的 state 于 finalReducers 下的 key 做比较,过滤出多余的 key
    const unexpectedKeys = Object.keys(inputState).filter(
        key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
    )

    unexpectedKeys.forEach(key => {
        unexpectedKeyCache[key] = true
    })

    if (action && action.type === ActionTypes.REPLACE) return

// 如果 unexpectedKeys 有值的话
    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.`
        )
    }
}

接下来让我们先来看看 compose 函数

// 这个函数设计的很巧妙,通过传入函数引用的方式让我们完成多个函数的嵌套使用,术语叫做高阶函数
// 通过使用 reduce 函数做到从右至左调用函数
// 对于上面项目中的例子
compose(
        applyMiddleware(thunkMiddleware),
        window.devToolsExtension ? window.devToolsExtension() : f => f
)
// 经过 compose 函数变成了 applyMiddleware(thunkMiddleware)(window.devToolsExtension()())
// 所以在找不到 window.devToolsExtension 时你应该返回一个函数
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)))
}

然后我们来解析 createStore 函数的部分代码

export default function createStore(reducer, preloadedState, enhancer) {
  // 第4个参数也是函数时,报错,多个增强函数可以合并成一个
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function.'
    )
  }
 // 一般 preloadedState 用的少,判断类型,如果第二个参数是函数且没有第三个参数,就调换位置
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

   // 判断 enhancer 是否是函数
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
     // 类型没错的话,先执行 enhancer,然后再执行 createStore 函数
    return enhancer(createStore)(reducer, preloadedState)
  }

  // 判断 reducer 是否是函数
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }
  // 当前 reducer
  let currentReducer = reducer
   // 当前状态
  let currentState = preloadedState
  // 当前监听函数数组
  let currentListeners = []
    // 这是一个很重要的设计,为的就是每次在遍历监听器的时候保证 currentListeners 数组不变
    // 可以考虑下只存在 currentListeners 的情况,如果我在某个 subscribe 中再次执行 subscribe
    // 或者 unsubscribe,这样会导致当前的 currentListeners 数组大小发生改变,从而可能导致
    // 索引出错
  let nextListeners = currentListeners

    // reducer 是否正在执行
  let isDispatching = false

  /**
   * This makes a shallow copy of currentListeners so we can use
   * nextListeners as a temporary list while dispatching.
   *
   * This prevents any bugs around consumers calling
   * subscribe/unsubscribe in the middle of a dispatch.
   */
  // 如果 currentListeners 和 nextListeners 相同,就赋值回去
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

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


  function observable() {
    const outerSubscribe = subscribe
    return {
      subscribe(observer) {
        if (typeof observer !== 'object' || observer === null) {
          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
      }
    }
  }

   // 然后在 createStore 末尾会发起一个 action dispatch({ type: ActionTypes.INIT });
 // 用以初始化 state
  dispatch({ type: ActionTypes.INIT })

  //dispatch一个用于初始化的action,相当于调用一次reducer
    //然后将reducer中的子reducer的初始值也获取到
    //详见下面reducer的实现。
  return {
    dispatch,
    subscribe,
    getState,
     //下面两个是主要面向库开发者的方法,暂时先忽略
    replaceReducer,
    [$$observable]: observable
  }
}

可以看出,createStore方法创建了一个store,但是并没有直接将这个store的状态state返回,而是返回了一系列方法,外部可以通过这些方法(getState)获取state,或者间接地(通过调用dispatch)改变state。
至于state呢,被存在了闭包中。

我们再来详细的看看每个模块是如何实现的

getState

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

    return currentState
  }

dispatch

 // 触发了一个action,因此我们调用reducer,得到的新的state,并且执行所有添加到store中的监听函数。
  function dispatch(action) {
     // 原生的 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?'
      )
    }
    // 注意在 Reducers 中是不能执行 dispatch 函数的
// 因为你一旦在 reducer 函数中执行 dispatch,会引发死循环
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }
// 执行 combineReducers 组合后的函数
    try {
      isDispatching = true
       //调用reducer,得到新state
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
// 然后遍历 currentListeners,执行数组中保存的函数
    const listeners = (currentListeners = nextListeners)
     //调用监听数组中的所有监听函数
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

subscribe

subscribe返回的是一个取消订阅的方法。取消订阅是非常必要的,当添加的监听器没用了之后,应该从store中清理掉。不然每次dispatch都会调用这个没用的监听器。

  // 添加一个监听函数,每当dispatch被调用的时候都会执行这个监听函数
  function subscribe(listener) {
    // 添加到监听函数数组,
    // 注意:我们添加到了下一次dispatch时才会生效的数组
    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
    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    //subscribe返回的是一个取消订阅的方法。
    //取消订阅是非常必要的,当添加的监听器没用了之后,应该从store中清理掉。
    //不然每次dispatch都会调用这个没用的监听器。

    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

      ensureCanMutateNextListeners()
      // 从下一轮的监听函数数组(用于下一次dispatch)中删除这个监听器。
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

在这之前我需要先来介绍一下函数柯里化,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

function add(a,b) { return a + b }
add(1, 2) => 3
// 对于以上函数如果使用柯里化可以这样改造
function add(a) {
        return b => {
                return a + b
        }
}
add(1)(2) => 3
// 你可以这样理解函数柯里化,通过闭包保存了外部的一个变量,然后返回一个接收参数的函数,在该函数中使用了保存的变量,然后再返回值。

在redux中使用中间件

还记得redux 的createStore()方法的第三个参数enhancer吗?

function createStore(reducer, preloadedState, enhancer) {
    if(enhancer是有效的){  
        return enhancer(createStore)(reducer, preloadedState)
    } 
    
    //...
}

在这里,我们可以看到,enhancer(可以叫做强化器)是一个函数,这个函数接受一个「普通createStore函数」作为参数,返回一个「加强后的createStore函数」。

applyMiddleware,顾名思义,「应用中间件」。输入为若干中间件,输出为enhancer。面来看看它的源码:

// 这个函数应该是整个源码中最难理解的一块了
// 该函数返回一个柯里化的函数
// 所以调用这个函数应该这样写 applyMiddleware(...middlewares)(createStore)(...args)
export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
      //用参数传进来的createStore创建一个store
    const store = createStore(...args)
     //注意,我们在这里需要改造的只是store的dispatch方法
    let dispatch = () => {
      //一个临时的dispatch
      //作用是在dispatch改造完成前调用dispatch只会打印错误信息
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }
     // 每个中间件都应该有这两个函数
      //接下来我们准备将每个中间件与我们的state关联起来(通过传入getState方法),得到改造函数。
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
     // 把 middlewares 中的每个中间件都传入 middlewareAPI
      //middlewares是一个中间件函数数组,中间件函数的返回值是一个改造dispatch的函数
        //调用数组中的每个中间件函数,得到所有的改造函数
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
     // 和之前一样,从右至左调用每个中间件,然后传入 store.dispatch

      //将这些改造函数compose(翻译:构成,整理成)成一个函数
        //用compose后的函数去改造store的dispatch
    dispatch = compose(...chain)(store.dispatch)

         // compose方法的作用是,例如这样调用:
        // compose(func1,func2,func3)
        // 返回一个函数: (...args) => func1( func2( func3(...args) ) )
        // 即传入的dispatch被func3改造后得到一个新的dispatch,新的dispatch继续被func2改造...



     // 返回store,用改造后的dispatch方法替换store中的dispatch
    return {
      ...store,
      dispatch
    }
  }
}
            


总结一下,applyMiddleware的工作方式是:

调用(若干个)中间件函数,获取(若干个)改造函数
把所有改造函数compose成一个改造函数
改造dispatch方法

中间件的工作方式是:

中间件是一个函数,不妨叫做中间件函数
中间件函数的输入是store的getState和dispatch,输出为改造函数(改造dispatch的函数)
改造函数输入是一个dispatch,输出「改造后的dispatch」

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容