Redux简介与源码分析

随着业务需求越来越复杂,应用中需要开发者管理越来越多的状态。这些复杂的状态也直接对应UI上的表现。如果不能很好的管理这些状态,我们就无法对应用的一些异常表现做出合理的解释。

Redux介绍

https://raw.githubusercontent.com/saymagic/pic/master/8f2eb073gy1fpzjp9tv8pj22sa15ego0.jpg

redux 的主要作用是管理业务上越来越多的状态。要理解 redux,就需要先了解它的几个主要概念:

State表示应用的状态。对应到JS中就是一个普通的 object。里面可以放置任何类型数据。

Action表示一个动作。可以是用户点击了某个按钮、服务器返回了结果等等。对应到JS中就是一个普通的 object。原生的 redux 要求这个 object 必须含有type属性。用于表示这个 action 的作用。

下面就是一个简单的 action:

const ADD_TODO = 'ADD_TODO'


{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

Reducer用于描述一个Action发生后,State该如何变化。对应到 JS 代码中reducer 是一个 function。函数的基本结构为:

function reducer(originState, action){
    return newState
}

ActionReducer之间并不会直接作用。Store的作用是将它们两个进行串联。Store 含有subscribe和 dispatch两个方法。dispatch方法接收Action作为参数,subscribe方法接收一个函数作为参数。

Store在创建的时候需要传入Reducer。当调用 dispatch 方法是,Store内部会通过Reducer会处理Action转化为新的State,subscribe函数就会接收到最新的State

一个简单的示例如下:

const unsubscribe = store.subscribe(() =>
  console.log(store.getState())
)

store.dispatch({type:'ADD_TODO', payload: 'Learn about actions'))

我们在了解了上面几个概念后再来看 redux,其实可以理解成观察者模式的特殊实现。在使用 redux 时有三条原则,也可以作为最佳实践:

Redux 源码分析

整个 redux 的源码包含注释仅有五百多行。

image

但整个代码写的相当漂亮。redux对外提供的方法和变量

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

下文,我们将分析几个主要的方法和变量的实现

createStore

createStore的函数原型为:

function createStore(reducer, preloadedState, enhancer)

函数的返回结果为:

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

首先解释三个参数:

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

    return enhancer(createStore)(reducer, preloadedState)
  }

如果我们传入了enhancer,直接返回调用enhancer的结果。关于enhancer,我们在后面的章节会详细分析applyMiddleware的实现。

再来一个一个看createStore函数返回的结果。dispatch、subscribe、getState、replaceReducer都是在createStore内部定义的函数。

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

    if (isDispatching) {
      throw new Error('')
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
        ...
    }
  }

subscribe接收一个函数参数,相当于观察者。isDispatching表示当前是否有 reducer 正在执行。该变量默认为 false。会在 dispatch 函数中置位 true。 主要看ensureCanMutateNextListeners函数,代码如下:

 let currentListeners = []
 let nextListeners = currentListeners

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

该函数的主要作用是重新生成一个nextListeners,该函数返回后,我们的 listener 会被直接 push 到nextListeners数组中。

  nextListeners.push(listener)

换句话说,nextListeners中保存着我们所有 subscribe传递进来的 listener。这里先留下一个疑问,为什么ensureCanMutateNextListeners会同时有currentListeners和nextListeners两个变量呢?只使用 nextListener 不可以吗?

最后,subscribe函数返回了一个unsubscribe函数,该函数用于取消订阅该 listener。这里不做展开。

dispatch 用于派发一个 action,源码如下:

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
  }

第一步,做了参数 action 的校验。

第二步,和 subscribe 函数类似,也判断了isDispatching变量是否为 true。

第三步,调用 reducer 转换为最新的状态:

try {
   isDispatching = true
   currentState = currentReducer(currentState, action)
} finally {
   isDispatching = false
}

一般来说,currentReducer就是 createStore 传入的reducer。同时可以注意到,isDispatching是在reducer 函数调用之前被置为 true 的,调用结束被置为 false。联想到刚刚和isDispatching有关的两处代码。在 reducer 函数调用的过程中,是不允许调用 subscribe和 dispatch 函数的。这样也是为了保证reducer的。防止 reducer 改变了外部状态,导致了程序的不确定性。

第四步,调用所有的 listener:

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

这里仅仅是对数组中所有函数进行逐一调用。需要注意的是,迭代的数组是当前时刻的nextListeners,同时currentListeners也指向了nextListeners。回到刚才我们提到的问题,currentListeners究竟是做什么的呢?

currentListeners的主要目的是解决 listener 函数中调用 subscribe 函数导致程序的不确定性。举例来讲,如果只有nextListeners,当迭代nextListeners时,如果listener中又触发了 subscribe 函数,此时,nextListeners的内容就会发生变化,整个 for 循环就不可控。

getState

  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
  }

第一步,判断isDispatching变量 第二步,直接返回currentState

replaceReducer

replaceReducer用于更换当前的 reducer

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

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

第一步,判断nextReducer参数类型 第二步,将currentReducer重置为nextReducer 第三部,派发ActionTypes.REPLACE事件

综上,我们就将整个 store 的入参和返回值都进行了分析。下面来看 Redux 中非常重要的一个部分 - 中间件。

applyMiddleware

applyMiddleware函数的代码比较少,但理解起来并不容易。

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }
    let chain = []

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

    return {
      ...store,
      dispatch
    }
  }
}

首先,applyMiddleware函数是一个高阶函数, 前文我们说过,createStore 函数中的一个入参 enhance是由applyMiddleware函数生成的。createStore中对 enhancer调用如下:

return enhancer(createStore)(reducer, preloadedState)

enhancer的调用完全符合applyMiddleware返回的函数原型。所以,我们可以想象,applyMiddleware函数调用完全结束后,返回的就是 store。

applyMiddleware函数前两阶的参数都是在createStore函数中传入的。最后一阶接收的参数是中间件数组。中间件也是函数,其原型为:

store => next => action => {

}

我们看最后一阶函数的实现,首先:

const store = createStore(...args)

这里的 store 实际上就是创造出原有的 store。紧接着,声明必要的变量:

let dispatch = () => {
  throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
}

let chain = []

const middlewareAPI = {
   getState: store.getState,
   dispatch: (...args) => dispatch(...args)
}

接下来的部分越来越烧脑:

chain = middlewares.map(middleware => middleware(middlewareAPI))

首先,middleware本身是一个三阶函数,调用middleware(middlewareAPI)之后是一个二阶函数。因此 chain 中是一个二阶函数的数组:

chain = [
	next1 => action1 => { middleware1 process },
	next2 => action2 => { middleware2 process },
	next3 => action3 => { middleware3 process },
]

紧接着:

dispatch = compose(...chain)(store.dispatch)

先看compose(...chain), 它的实现在compose.js文件中:

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

compose 意为组合,该compose的实现是使用 reduce,相当于从右到左来组合多个函数。 compose 实现的效果是:

compose(f1, f2, f3)(arg) = f1(f2(f3(arg)))

compose(…chain)(dispatch)生成的 dispatch 就是:

dispatch = action1 => { middleware1 process } (
	action2 => { middleware2 process } (
		action3 => { middleware3 process } (dispatch)
	)
) 

所以,当调用dispatch(action)时,middleware1 最先收到action,它可以对 action 进行处理,或者选择调用下一级(next2):

// next 2

(
	action2 => { middleware2 process } (
		action3 => { middleware3 process } (dispatch)
	)
) 

综上,当compose(...chain)(dispatch)完成后,我们就得到了一个增强的 dispatch。最后,applyMiddleware 函数返回的 store 也仅仅更改了原有 store 的 dispatch:

return {
  ...store,
  dispatch
}

参考

https://redux.js.org/

https://github.com/reactjs/redux