讲讲redux实现的精华和细节

266 阅读3分钟

注:本文不会仔细分析redux-react

middleware究竟是什么?

first look code!
const middlewareAPI: MiddlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
return dispatch

compose的写法很美妙(FIFO遍历):

compose = middlewares.reduce((a,b) => (...args) => b(a(...args)))

redux的实现是FILO

compose = middlewares.reduce((a,b) => (...args) => a(b(...args)))
// FIFO和FILO的实现 取决于最内层函数是第一个中间件还是最后一个中间件
// 这里实现非常巧妙,递归倒换执行顺序,省去了reverse,可以学习一下

再次强调,compose的写法很美妙,我们详细分析一下

  • 每次reduce里b(...args)其实就是middleware的next,store.dispatch是最后一个中间件处理完成后的调用的真实dispatch,此时action已经纯化
  • 以FILO的形式遍历栈,为每个中间件传入next
  • 重点:返回第一个middleware的最终函数,即 action => {}

初次看compose会觉得很复杂,写一个容易理解的版本

dispatch = (...middlewares) => {
    middlewares.reverse()
    let next = store.disptach
    middlewares.forEach((middleware,i) => {
        middlewares[i] = middleware(next)
        next = middlewares[i]
    })
    return _.last(middlewares)
}

到这里,理一下dispatch的逻辑:

  • 遍历中间件,为每一个中间件curry一个middlewareAPI进去
  • 遍历中间件,为每一个中间件传入next,并返回第一个中间件
  • 此时dispatch就是串联好的中间件组的头部

所以middleware是action -> reducers 这个过程中对数据的处理函数

想一想thunk,saga是不是这样

附:
export interface Dispatch<A extends Action = AnyAction> {
  <T extends A>(action: T, ...extraArgs: any[]): T
}

export interface AnyAction(extends Action){
	type:T;
  [extraProps: string]: any;
}

createStore做了什么?

createStore定义
export default function createStore(
  reducer: Reducer<S, A>,
  preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
  enhancer?: StoreEnhancer<Ext, StateExt>
){

store有几个重要的方法

  • subscribe (redux-react就是在compose函数里为每个组件subscribe了patch,再由patch触发的组件更新)
  • dispatch

subscribe不能在dispatch时执行,所以两个一起讲

#####dispatch一些核心代码

//...
try {
  isDispatching = true
  currentState = currentReducer(currentState, action) // 这就是为什么reducer不能是异步
} finally {
	isDispatching = false
}
// try finally 只是为了清除isDispatching表示,错误依然由调用者处理
//...

// 对应下面subscribe的ensureCanMutateNextListeners
const listeners = (currentListeners = nextListeners)

// 触发订阅者响应,redux-react里rerender订阅组件
for (let i = 0; i < listeners.length; i++) {
  const listener = listeners[i]
  listener()
}
sub/unsubscribe一些核心代码
if (isDispatching) throw new Error(...) // 首先不能在dispatch里subscribe,避免影响订阅队列

let isSubscribed = true
ensureCanMutateNextListeners() // 如果在下次dispatch之前再次新增订阅,不复制next缓冲
nextListeners.push(listener)

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

// 重点提一下ensureCanMutateNextListeners函数
/*store维护了nextListener和currentListener两个类,以保证在dispatch时执行的是一个snapshot,*	而非nextListener(最新值)。这只是为了处理某种极端情况
*	该情况如下,某个listener里进行了unsubscribe,此时listeners数组会产生变化,for循环遍历将会出现问题
*/

参考该issue

事实上,subscribe运用了双缓冲技术,防止该次处理时订阅队列发生变化。这种技术在V8 GC 里cheney算法,以及fiber架构上都有广泛运用

Tools函数

bindActionCreators

Action Creator 很简单,就是一个创建 action 的函数。不要混淆 action 和 action creator 这两个概念。Action 是一个信息的负载,而 action creator 是一个创建 action 的工厂。

react应用里,bindActionCreators的作用是让用户无redux感知的调用dispatch

总结

其实redux就是两个很简单的逻辑

  1. reducer(middleware(dispatch(action)))
  2. emit subscribe

虽然细节上有值得我们学习的地方,但整体上只是flux的简单实现,真正的精髓在于结合react该如何应用。

redux已经成了一个闭环,在react应用里,redux-store就是一个黑盒,如何做到利用这个非响应式的仓库完成尽量精确的状态依赖,才是一系列redux-react的核心。那也才是真正值得学习的源码,以后有机会会讲讲react-redux库和mobx-react-lite的源码。

觉得还行点个赞吧。。。