浏览redux源码

redux源码剖析

什么是redux

Redux 是 JavaScript 状况容器,供应可展望化的状况治理。

为何须要运用redux

供应了和双向绑定头脑差别的单向数据流,运用状况能够展望,能够回溯,易于调试。运用redux之初的人可能会很不顺应,转变一个状况,最少写三个要领,从这点上不如写其他框架代码易于邃晓,然则自从合营运用redux-logger一类的logger插件,就觉获得了redux的上风。状况转变很清楚,很轻易相识发生了什么。

源码剖析

注重: 假如没有运用过redux,发起先去看看redux文档

api要领

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}

能够看到我们在react代码中运用到的api,平常主动挪用的就是 combineReducers ,其他部份参照例子基础能够搬过来

combineReducers

翻开combineReducers.js,先看export的要领,也就是combineReducers要领

  var reducerKeys = Object.keys(reducers)
  var finalReducers = {}
  for (var i = 0; i < reducerKeys.length; i++) {
    var 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

  var finalReducerKeys = Object.keys(finalReducers)

  if (process.env.NODE_ENV !== 'production') {
    var unexpectedKeyCache = {}
  }

  var sanityError
  try {
    assertReducerSanity(finalReducers)
  } catch (e) {
    sanityError = e
  }

这里彷佛还在推断这个末了reducers的合法性,那这里是在推断什么呢?我们来看看 assertReducerSanity 要领

function assertReducerSanity(reducers) {
  Object.keys(reducers).forEach(key => {
    var reducer = reducers[key]
    var 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.`
      )
    }

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

这块实在就两个推断,reducer被实行了两次,一个是推断没有初始化state的,reducer的返回值,一个推断action没有type的时刻的返回值。一个没有返回值都邑有正告,所以我们写reducer的时刻都邑指定一个默许返回值。

reducer会被实行屡次,这也是我们为何要保证reducer的地道性,不能做任何其他的操纵的缘由

继承往下看 combineReducers

能够看到返回了一个函数 combination(state = {}, action) 。为何返回函数呢?

那我们看 combination(state = {}, action) 像什么?不就是我们常常写的reducer嘛!这个reducer终究会被store传入初始state而且看成纯函数挪用,而reducer内里是能够嵌套combineReducers的效果的,所以我们在运用状况的时刻,常常会如许 state.user.login 如许子的相似状况挪用

这块想邃晓照样有点庞杂,一切的reducer都是一个雷同的函数combination,吸收state参数,内部实行一样是combination,直到没有combineReducers为止,才最先实行我们本身写的reducer函数,获得的值运用combineReducers参数的对象的key作为state的key,我们本身写的reducers实行效果获得的值作为state的value。终究获得的就是一个庞大的Object,这就是我们的store中的state。

createStore

平常这个要领我们能够直接从demo中复制过来,不须要太甚相识,然则既然要深切相识redux,必定要控制这个要领

跟之前一样,先找到 export createStore 要领,能够看到这个函数接收三个参数

export default function createStore(reducer, preloadedState, enhancer) {

第一个reducer: 上文讲到的combineReducer返回的reducer函数

第二个preloadedState:redux初始化state,能够不传

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

能够看到第一个推断的意义是当没有第二个参数是函数的时刻,默许第二个参数就是中心件,而且默许state置为undefined

第二个推断的意义是当有中心件参数,然则中心参数范例不是function的时刻,抛出一个不法毛病,假如是函数,先实行中心件,退出。后续在讲中心件是怎样实行的

第三个推断reducer是不是是函数,不然抛出毛病退出

  var currentReducer = reducer         // 当前reducer
  var currentState = preloadedState    // 当前state
  var currentListeners = []            // 当前监听器
  var nextListeners = currentListeners // 下一个监听器
  var isDispatching = false            // 反复dispatch的状况标记

再看看createStore的返回值

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

这不是store的要领嘛,挨个看看

  function getState() {
    return currentState
  }

这个没什么好说的。

  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    var isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      var 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
    }

    var listeners = currentListeners = nextListeners
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]()
    }

    return action
  }

老几样啊,先做一些推断,我们写代码的时刻彷佛没这么严谨哈。实行reducer,触发一切listeners。这个比较简单。

如许子,看起来createStore没什么庞杂的,庞杂的在哪呢?我们擦过的中心件退出的环节。所以来烧脑吧,看看中心件

想一想我们建立store的时刻是怎样操纵的

  const finalCreateStore = compose(
    applyMiddleware(thunk, logger)
  )(createStore)

  const store = finalCreateStore(rootReducer, initialState)

这类堆在一起的代码不是太好看,离开,离开

const middlewares = applyMiddleware(thunk, logger)
const composeResult = compose(middlewares)
const finalCreateStore = composeResult(createStore)
const store = finalCreateStore(rootReducer, initialState)

这就条理清楚多了,看代码一定要看懂流程,根据递次看,不然一头雾水,先看第一步 applyMiddleware

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

能够看到这个要领返回一个函数,既然这个函数没有被实行到,我们就先不看,如今我们获得了一个 applyMiddleware 返回的函数了

接着看 compose 要领了

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

代码更少,但是redux精华全在这了。

compose 实行吸收参数,假如参数个数是1,直接实行,上文的 applyMiddleware 的实行效果返回值是一个函数middlewares,作为参数的话,长度确实是1,所以直接返回了middlewares,也就是composeResult,所以这块是不须要compose的。而这个参数函数吸收一个参数就是createStore,恰好吸收createStore要领,所以我们照样进入到 applyMiddleware 的返回函数内里看看

明显 composeResult 吸收到 createStore以后返回一个函数: finalCreateStore,从代码中能够看出也是能够吸收中心件要领的,不过应当不会有人再在这里反复增加中心件了。

进入到 finalCreateStore 中看看

  • 建立了store,前文已讲过了

  • 把一切的middlewares实行一遍,从这里能够看出middlewares是一个吸收 { dispatch, getState } 参数的函数,不可能有其他状况

  • 把middlewares实行的效果数组作为参数再一次传入了compose

再次进入到 compose 中看逻辑,假如只要一个中心件的话,一样是把中心件直接返回,假如凌驾一个实行下面的逻辑

  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))

compose 一样只是返回了一个函数。这个函数吸收的参数在 applyMiddleware 内里能看到吸收到的是dispatch要领

这里奇妙的利用了js Array的reduce要领,reduce要领的道理就是回调函数的返回值作为后一个回调函数的第一个参数,第一个回调函数的第一个参数的值是 reduce要领的第二个参数值。

args就是dispatch要领,这里看的出中心件函数还得返回函数,这个函数得吸收相似dispatch要领的函数

看看redux-chunk这个中心件的完成吧

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

看到 next 要领,运用过express的同砚应当会很熟习,这个next和express的next很像,道理也相似。

每一个中心件的末了一层函数都是一个next,才能够在reduce内里作为参数通报,才能够完成中心件的通报

这也是redux称号的由来。

redux代码短小精悍,设想精致,真好。

    原文作者:frontoldman
    原文地址: https://segmentfault.com/a/1190000009146720
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞