HomePostsWorks

Redux 源码解析

createStore

有 middleware 的情形后面再讨论,这里先讨论最简单的情形。

首先设置了初始状态 ,还有其他一些属性,通过闭包封装。

let currentReducer = reducer let currentState = preloadedState let currentListeners = [] let nextListeners = currentListeners let isDispatching = false

另外这个作用域内声明了这些函数:

最终被封装在对象 上返回,就是我们通常说的 Store 对象。

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

Middleware

如果要使用中间件的话,在 createStore 的过程中,就会调用这一行代码 

// [A] return enhancer(createStore)(reducer, preloadedState)

这里的 enhancer 实际上是 applyMiddleware 函数的返回值,接下来就看一看这个函数。

export default function applyMiddleware(...middlewares) { return /* B */ (createStore) => /* C */ (...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.' ) } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } const chain = middlewares.map((middleware) => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }

redux-thunk 也算是官配了 ,而且它还非常简短,这里我们就拿 来举例子方便大家更好地理解:

function createThunkMiddleware(extraArgument) { return // [1] ;({ dispatch, getState }) => /* [2] */ (next) => /* [3] */ (action) => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument) } return next(action) } } const thunk = createThunkMiddleware() thunk.withExtraArgument = createThunkMiddleware export default thunk

使用这个 middleware 的时候如下所示:

createStore(reducer, applyMiddleware(thunk))

我们现在开始仔细地分析调用过程:

  1. applyMiddleware 函数接收多个 middleware,然后返回了一个函数 B
  2. A 中第一次调用的时候,createStore 方法被传递给函数 B,再次返回了一个函数 CC 再被 [A] 中的第二次调用所调用
  3. createStore 方法创建了一个真正的 Store 对象。
  4. 这一行代码 注册这些 middleware(当然在这里我们只有 thunk 这一个中间件),可以看到 getStatedispatch 这两个方法就被传给了函数 1,返回的是函数 2
  5. 这个函数 2compose 函数所用,生成了一个新的 dispatch 方法 3,注意,这个方法是用户调用的 dispatch它才是真身!)
  6. 最后的返回看起来像是个 Store 对象,其实是个“套壳”的 Store

我们先讲解 compose 方法,然后再来讲解用户调用 dispatch 时会发生什么。

compose

compose 函数负责将 dispatch 过程串起来。通过上面的分析我们已经知道了被送给 compose 方法的是形如这样的一组函数:

;(next) => (action) => { // ... 中间件对 action 进行处理 }

compose 的全部代码如下,它对 middleware 的数目分为三种情况来处理:

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

我们先来考虑没有函数的情形,这种情况下返回一个透传函数 arg ⇒ arg,即不对原始的 dispatch 做任何改变:

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

接下来考虑有一个中间件的情形,这种情况下直接返回中间件:

;(next = store.dispatch) => (action) => { // next 就是 store.dispatch // ... 中间件对 action 进行处理 // 如果要交给原始的 dispatch 就调用 next }

下面我们来考虑最复杂的情形,即有多个中间件,这里 compose 将它们用 reduce 串接起来:

return funcs.reduce( (a, b) => (...args) => a(b(...args)) )

这样的写法可能具有误导性(让读者误以为 ab 都是中间件),我们将形参改个名字理解起来会更容易:

return funcs.reduce( (alreadyChained, nextMiddleware) => (...args) => alreadyChained(nextMiddleware(...args)) )

可以看到最终形成的是一种链式调用,如果我们这样调用 compose 方法:

compose(a, b, c)

最终就会得到:

;(...args) => a(b(c(...args)))

这样用户在调用 dispatch 的时候,中间件只要调用 next 就能将 action 抛给下一个中间件处理。

用户调用 dispatch 时会发生什么

现在我们可以来探究用户调用 dispatch 时会发生什么了。我们已经知道了用户实际调用的是 compose 函数的返回值,所以实际上执行的是函数 [4],而我们知道在用 redux-thunk 的时候,action 里面会调用 disaptch,而这个 dispatch 就是 [4] 本身!只不过 [4] 第二次被调用的时候,走的是 next(action),而我们在上面分析过,这里 next === store.dispatch,这样就会到真正 Store 对象的 dispatch 方法啦。

下面这张图能够帮助你理解变量之间的关系:

call

combineReducers

combineReduces 也是个十分重要的函数,随着应用规模的扩大,你会希望不同的 reducer 能够处理状态树的局部而非一个 reducer 管理整个状态树。这个函数的代码在这里 ,其名十分贴切,工作原理就像 mapReduce。

首先这个方法会校验参数的正确性,然后返回一个名为 combination 的大 reducer。

combination 一开始仍然是校验参数的正确性,真正执行 mapReduce 过程的只有这几行 。可以看到它会分把先前状态的一部分摘取下来放到名为 previousStateForKey 的变量里,然后通过对应的 reducer 来产生局部的新状态,赋值到新的整体状态上,然后,判断局部的状态是否发生变化,从而判断整体的状态是否发生变化。

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) nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length return hasChanged ? nextState : state

总结

以上就是对 redux 源码的解读了。可以看到除了串联 middleware 的部分,都非常清晰易懂。对于 Redux 这样的库来说,其思想远比其实现精妙的多。

另外还有一个函数 bindActionCreators 这里就不介绍了,感兴趣的话可以自行阅读其源码 

CC BY-NC 4.0 2025 © Evan HuRSS