初步解读 Redux 源码理解其原理

Redux github 地址

前言

Redux 源码的体量算是比较小的了,但是详细解读还是有点压力,在此简单解读下,目的是能大概理解其原理。直接看导出的 API:createStorecombineReducersbindActionCreatorsapplyMiddlewarecompose,其中 bindActionCreators 暂时不想理会,余下的依次介绍。

createStore

关键代码(省去一些加强健壮性的代码)以及对应的解读注释如下:

// 函数接受三个参数,第一个是 reducer,第二个是初始 state,第三个是由 applyMiddleware
// 生成的 enhancer
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) // 可以看出 enhancer
    // 是一个以 createStore 为参数然后返回一个 createStore 的高阶函数,待会儿通过
    // applyMiddleware 可以看到这个 enhancer 到底是怎么起作用的
  }
  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

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

  function getState() { // 返回当前的 state
    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
  }

  function subscribe(listener) { // 订阅函数,每当 dispatch 的时候 listeners 都会执行
    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)

    // 返回值是一个可以取消当前 listener 订阅的函数
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
        )
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  // dispatch,通过这个函数 dispatch action 借由 reducers 来生成一个新的 state
  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++) { // 如前所述,每 dispatch 一个 action 时
      // 都会去执行所有的 listeners
      const listener = listeners[i]
      listener()
    }

    return action // 返回值和传入的参数一样,action
  }

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

    currentReducer = nextReducer
    // replace reducer 后 dispatch  一个 REPLACE action
    dispatch({ type: ActionTypes.REPLACE })
  }

  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.
  // store 生成后 dispatch 一个 INIT 的 action
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer
  }
}

createStore 主要的作用就是根据提供的 reducerinitialStateenhancer 生成 store,然后 store 可以提供 dispatchsubscribegetStatereplaceReducer等方法。

combineReducers

combineReducer 的作用是将多个(如果有的话) reducer 整合成一个总的 reducer,关键代码:

// reducers 是一个 plain object,一个 key 对应一个 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]

    // 过滤无效的 reducer
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  // 返回值依然是一个以 state 和 action 为参数的 reducer,但是它可以处理所有的 type 的 action
  return function combination(state = {}, action) {

    let hasChanged = false
    const nextState = {}
    // 具体处理 action 的做法是,把每个 action 代入所有 reducer 生成对应的结果,然后再整合
    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)

      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

compose

这个方法的代码最少:

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

funcs是一个函数数组,reduce是数组的归并方法。这个方法接受一系列的函数作为参数,而且这一系列函数,从右到左,上一个函数的返回值可以为下一个函数的参数,最终返回一个以最右边的函数的参数为参数(这个参数再依次交给左边的函数处理,返回后继续此步骤,一直到最左边)以最左边的函数的返回值为返回值的复合函数。比如:

 const F = compose(f1, f2. f3, f4, ..., fn)
 F //f1(f2...(fn(...args)))

applyMiddleware

applyMiddleware,可以说这个方法为 Redux 提供了各种可能性,关键代码:

import compose from './compose'

export default function applyMiddleware(...middlewares) {
  // 返回值是一个同时以 createStore 为参数和返回值的闭包
  return createStore => (...args) => {
    const store = createStore(...args) // 这里的store与没有enhancer时的并无二致
    let dispatch = () => {
      throw new Error( // 中间件中不允许 dispatch
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }
    
    // 中间件API,规定了 middleware 是一个以 { getState, dispatch } 为参数的函数
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }

    // chain 的每个元素依然是一个函数,经过 compose 作用后返回一个合成函数,合成函数以
    // store.dispatch 为参数,最终生成一个加强了的 dispatch
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)
    // 上面语句分开写就是 const composedFn = compose(...chain)
    // dispatch = composedFn(store.dispatch) 
    // 其中 composedFn: (...args) => chain[0](chain[1](...(chain[length - 1](...args))))
    // 可以看到,`createStore` 经过 `storeEnhancer` 加强之后,其实只是用新的`dispatch` 将原来
    // 的 `dispatch` 替换,其他的部分保持不变
    return {
      ...store,
      dispatch
    }
  }
}

这个方法接受一系列的 Redux 的 middleware 为参数,然后返回一个以 createStore 为参数的 storeEnhancer,其实 storeEnhancer enhance 的是 dispatch(这里好像并不准确,因为除了由中间件生成的 storeEnhancer 以外,还有其他的 storeEnhancer,而这些 storeEnhancer 就有更强的功能,比如像 devToolsExtension 这样的扩展工具)。由于每个 middleware 在作用 { getState, dispatch } 后可以被 compose 处理,那我们可以知道 middleware({ getState, dispatch }) 的返回值是一个函数,而且这个函数的参数和返回值是具有相同签名的函数,于是 middleware 的函数签名大概是:({ getState, dispatch }) => next => action,其中 next(action) 表示将 action 交由下一个 middleware 处理,最后一个 middleware 的 nextdispatch

举个例子:

//m1, m2, m3 是三个中间件
const middlewares = [m1, m2, m3]

const storeEnhancer = applyMiddleWare(...middlewares)
const store = createStore(reducer, {}, storeEnhancer)

export default store

store.dispatch 被强化的过程是这样:
普通 dispatch -> 被 m3 强化后的 dispatch(记为 m3(dispatch)) -> 再被 m2 强化后的 dispatch(记为 m2(m3(dispatch))) -> 再被 m1 强化后的 dispatch(记为 m1(m2(m3(dispatch)))
对应地,加载了上述中间件的 store dispatch 一个 action 的过程是这样:
m1(m2(m3(dispatch)))(action) -> next(action)next === m2(m3(dispatch)))-> next(action)next === m3(dispacth))-> next(action)next === dispatch)。

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

推荐阅读更多精彩内容