Redux 源码剖析

Redux 源码剖析

Redux自己只暴露了5个API,分别是:typescript

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes  // redux内部使用的action-type,可忽略
}
复制代码

这篇文章将每一个api的职责一一作个剖析总结,redux自己可用一句话归纳:redux

可预测的状态管理容器api

createStore

createStore是redux核心须要调用的,返回一个store对象,包含针对全局状态树的“增删改查”操做,它主要有两种调用方式:createStore(reducer, preloadState?)createStore(reducer, preloadState?, enhancer?)数组

  • reducer(state, action): 根据指定action返回一个新的state, 更改状态的核心逻辑所在
  • preloadState: 初始全局状态(object)
  • enhancer: 一个函数,返回一个增强版的store(主要针对dispatch函数)

后面两个参数是可选参数,createStore调用后返回一个store对象:markdown

store = {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
}
复制代码

接下来咱们深刻createStore函数内部,若是enhancer第三个参数存在并是一个函数,或者调用createStore只有两个参数且第二个参数是个函数,则会有如下逻辑:app

if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    return enhancer(createStore)(reducer, preloadedStat)
}
复制代码

能够看到直接调用enhancer并返回了它,返回的就是一个与上面结构一致的增强版store对象,具体enhancer的实现咱们在讲到applyMiddlewareAPI的时候在谈。若是enhancer不存在的话,就会执行如下逻辑(简单起见我写了个简易版的实现,其余具体会在后面一块儿讲解源码的设计细节):less

function createStore(reducer, preloadState) {
    let state = preloadState
    let listeners = []
    function getState() {
        return state
    }
    function subscribe(listener) {
        listeners.push(listener)
        return function unsubscribe() {
            var index = listeners.indexOf(listener)
            listeners.splice(index, 1)
        }
    }
    function dispatch(action) {
        state = reducer(state, action)
        listeners.forEach(listener => listener())
        return action
    }
    dispatch({})
    return { dispatch, subscribe, getState }
}
复制代码
  • getState(): 返回当前的全局状态对象
  • subscribe(listener): 订阅一个监听器(函数)
  • dispatch(action): 根据指定的action执行对应的reducer逻辑并返回最新的state,而后执行订阅的全部监听器,也是更改全局状态的惟一方法

在listener回调函数中须要调用store.getState()拿到最新的state,在执行其余逻辑,关于为何不把state当成参数直接给每一个listener回调,能够看看这个FAQ,上面就是redux的最原始简单实现,你们应该都能看懂,但确定上不了生产的,有不少注意点须要专门提出来讲下函数

首先在reducer中能够再次进行dispatch调用吗?源码设计中是不能够的,显然若是能够在reducer中再次执行dispatch操做,对性能是一个很大的隐患,会屡次对listeners中的监听器进行屡次渲染,所以社区也有不少插件其实也是能够进行批量dispatch的,例如redux-batch, redux-batched-actions,这些都是上面提到的提供了enhancer函数也就是一个增强版的dispatch函数,后续会提到enhancer。所以,redux的dispatch方法源码中有如下逻辑:工具

const isDispatching = false
function dispatch(action) {
    // ...
    if (isDispatching) {
        throw new Error('Reducers may not dispatch actions.')
    }

    try {
        isDispatching = true
        state = reducer(state, action)
    } finally {
        isDispatching = false
    }
    // ...
}
复制代码

值得一提的是isDispatching变量也在subscribeunsubscribe中使用了,也就是说,在reducer中也是不能进行store.subscribe()和取消订阅操做的。oop

在想一下,能够在listener监听函数中再次执行store.subscribe()吗?想一下应该是能够的,但是看下咱们上面的简易实现,若是在forEach循环的listener中再次执行listeners.push(listener)或者调用相应的unsubscribe函数可能会致使bug,由于push和splice操做都是改变了原数组。显然,这里须要两个listeners来防止这种状况出现,在源码中声明了两个变量以及一个函数:

let currentListeners = []
let nextListeners = currentListeners

// 拷贝一份currentListeners到nextListeners
function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
}
复制代码

而后在subscribe函数体中,以及返回的unsubscribe中:

function subscribe(listener) {
    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
        ensureCanMutateNextListeners()
        const index = nextListeners.indexOf(listener)
        nextListeners.splice(index, 1)
        currentListeners = null
    }
}
复制代码

这里订阅和取消订阅操做都是在nextListeners上进行的,那么dispatch中就确定须要在currentListeners中进行循环操做:

function dispatch(action) {
    // ...
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
    // ...
}
复制代码

如此设计就会避免相应的bug,但这样有一个明显的点要记着的就是,每次在listener回调执行订阅操做的一个新listener不会在这次正在进行的dispatch中调用,它只会在下一次dispatch中调用,能够看做是在dispatch执行前的listeners中已经打了个快照了,此次的dispach调用中在listener回调中新增的listener只能在下个dispatch中调用,由于currentListeners里尚未最新的listener呢

能够看到store中还返回了一个replaceReducer方法,直接粘贴源码以下:

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

    dispatch({ type: ActionTypes.REPLACE })
    return store
}
复制代码

replaceReducer方法自己的逻辑就如此简单,正如字面意思就是替换一个新的reducer,dispatch({ type: ActionTypes.REPLACE })这行与上面的简版代码dispatch({})效果相似,每当调用replaceReducer函数时都会以新的ruducer初始化旧的state并产生一个新的state,它比较经常使用于动态替换reducer或者实现热加载时候使用

compose

compose函数在redux中的职责更像是一个工具函数,能够把它想象成一个组合器,专门将多个函数组合成一个函数调用,源码也很简单,以下:

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方法将多个函数汇聚成一个函数,可将它看做成这个操做:compose(a, b ,c) = (...arg) => a(b(c(...arg))),从这个形式能够看出传给compose函数的参数必须都是接受一个参数的函数,除了最右边的函数(即以上的c函数)能够接受多个参数,它暴露出来成一个独立的api多用于组合enhancer

applyMiddleware

applyMiddleware函数能够说是redux内部的一个enhancer实现,它能够出如今createStore方法的第二个参数或第三个参数调用createStore(reducer, preloadState, applyMiddleware(...middlewares)),redux相关插件基本都要通过它之口。它接受一系列的redux中间件applyMiddleware(...middlewares)并返回一个enhancer,咱们先来看下它的源码:

function applyMiddleware(...middlewares) {
  return createStore => (reducer, ...args) => {
    const store = createStore(reducer, ...args)
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
复制代码

以上就是applyMiddleware的全部源码,看redux源码的全部api设计就有个感受,看起来都很精简。咱们从它的参数看起,它接受一系列的middleware,redux官方说middleware的设计应遵循这样的签名({ getState, dispatch }) => next => action,为何要这样设计呢?咱们一步一步来看,先看下它的返回值,它返回的是一个函数,相似这样的签名createStore => createStore,从以上代码来看相似这种createStore => (reducer, ...args) => store,因此这也经常被用做createStore的第三个参数存在,还记得咱们在讲createStore函数时说了,如果enhancer存在会有以下逻辑:

if (typeof enhancer !== 'undefined') {
  if (typeof enhancer !== 'function') {
    throw new Error('Expected the enhancer to be a function.')
  }
  return enhancer(createStore)(reducer, preloadedStat)
}
复制代码

恰好对应于咱们上面写的函数签名,也就是说若是enhancer不存在,redux会建立内部的store,若是存在,就先建立本身内部的store,而后将store传给中间件进行“魔改”,“魔改“什么呢?dispatch函数,看一下它最后的返回值return { ...store, dispatch }覆盖了原生的dispatch方法,但并非说原生的dispatch方法不见了,它只是通过中间件而被加工赋予了更多的功能,接着往下看最核心的两行

const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
复制代码

从第一行很容易能够看出它执行了众多中间件,以getState和dispatch方法为命名参数传递给中间件,对应于咱们上面说的这样的签名({ getState, dispatch }) => xxx,这样上面的chain常量就应该是这个样子[next => action => {}, next => action => {}, xxx],最关键的就是上面第二行代码了,compose函数咱们已经解释过了,假设chain常量是这样[a, b],那么就会有以下代码:

dispatch = ((...args) => a(b(...args)))(store.dispatch)
复制代码

看了上面的代码,可能有的人以为看着更复杂了,其实compose(...chain) = (...args) => a(b(...args)),而后就是个当即执行函数

(a => {
  console.log(a)  // 当即打印 1
})(1)
复制代码

而后从上面咱们就能够得出这块代码:dispatch = a(b(store.dispatch))这里咱们就要去解释为何middleware要设计成这样({ getState, dispatch }) => next => action,咱们在进行一步一步拆解以下:

b(store.dispatch) = action => {
  // store.dispatch可在此做用域使用,即 next
}
复制代码

action => {}不就是redux中dispatch的函数签名嘛,因此b(store.dispatch)就被当成一个新的dispatch传递给a(),a在以同种方式循环下去最终赋给dispatch,值得注意的是每一个中间件最终返回值应这样写return next(action),最终

dispatch = action => next(action)
复制代码

action变量信息就会随着next一步步从a到b穿透到最后一个中间件直至被redux内部的store.dispatch调用,也就是最终修改reducer逻辑,store.dispatch最终返回的仍是action,这就是中间件逻辑,你能够在中间件中任什么时候候调用next(action),最终返回它就好了,咱们能够看个redux官网的小例子:

function logger({ getState }) {
  return next => action => {
    console.log('will dispatch', action)
    // Call the next dispatch method in the middleware chain.
    const returnValue = next(action)
    console.log('state after dispatch', getState())

    // This will likely be the action itself, unless
    // a middleware further in chain changed it.
    return returnValue
  }
}

const store = createStore(todos, ['Use Redux'], applyMiddleware(logger))

store.dispatch({
  type: 'ADD_TODO',
  text: 'Understand the middleware'
})
// (These lines will be logged by the middleware:)
// will dispatch: { type: 'ADD_TODO', text: 'Understand the middleware' }
// state after dispatch: [ 'Use Redux', 'Understand the middleware' ]
复制代码

combineReducers

combineReducers也能够算个工具函数,它旨在把一个以对象形式的多个reducer合并成一个reducer传给createStore方法,相似以下:

const reducer = combineReducers({
  foo: (fooState, action) => newFooState,
  bar: (barState, action) => newBarState,
  ...,
})
const store = createStore(reducer)
复制代码

在一些大型复杂场景中应用仍是挺普遍的,可将全局状态分离成一个字状态方便维护,咱们来看下它的源码实现:

function combineReducers(reducers) {
  const finalReducers = { ...reducers }
  const finalReducerKeys = Object.keys(finalReducers)

  return function combination(state = {}, action) {
    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
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}
复制代码

省去了一些类型判断和报错信息的逻辑,只保留了核心的实现。关于它的入参和返回值上面已经说过了,咱们着重来看下返回的combination实现,当你在业务代码中每次dispatch一个action的时候,这最终的combination reducer就会循环遍历子reducer,从for 循环中const nextStateForKey = reducer(previousStateForKey, action)就能够看出来它将计算出的子新state存在nextState中,这里有个点要注意的就是咱们的子reducer须要处理传入的state为undefined的状况(state的默认值是{}),并且子reducer的返回值也不能是undefind,常见的处理状况就给个默认值就行(state = initialState, action) => xxx

还注意到hasChanged变量的做用,它在每次的for 循环中只要返回的新state与旧state不一样就为true,循环外还判断了下整个过程有没有新增或删除的reducer,为true就返回新的nextState,false返回原有state,这基本上就是combineReducers的实现逻辑,也不复杂

bindActionCreators

bindActionCreators函数也算是个工具函数,了解了上面的api源码结构,看它的做用也就知道了,以下:

function bindActionCreator(actionCreator, dispatch) {
  return function(this, ...args) {
    return dispatch(actionCreator.apply(this, args))
  }
}

function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  const boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
复制代码

这个跟随官方的小例子,一看就会明白怎么使用了~

总结

redux源码很好读~尤为如今仍是用typescript书写,各个函数的接口类型都一清二楚,我的以为比较复杂难想的就是中间件的设计了,不过从头看下来能感受到源码的短小精炼,其中也是收获蛮多~


原文连接

相关文章
相关标签/搜索