Closed wzhudev closed 3 years ago
有 middleware 的情形后面再讨论,这里先讨论最简单的情形。
首先设置了初始状态,还有其他一些属性,通过闭包封装。
let currentReducer = reducer let currentState = preloadedState let currentListeners = [] let nextListeners = currentListeners let isDispatching = false
另外这个作用域内声明了这些函数:
reducer
dispatch
isDispatching
true
isPlainObject
obj
Object
currentReducer
currentState
最终被封装在对象上返回,就是我们通常说的 Store 对象。
Store
return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable }
如果要使用中间件的话,在 createStore 的过程中,就会调用这一行代码:
createStore
// [A] return enhancer(createStore)(reducer, preloadedState)
这里的 enhancer 实际上是 applyMiddleware 函数的返回值,接下来就看一看这个函数。
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));
我们现在开始仔细地分析调用过程:
B
A
C
[A]
getState
1
2
compose
3
我们先讲解 compose 方法,然后再来讲解用户调用 dispatch 时会发生什么。
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 做任何改变:
arg ⇒ arg
dispatch = compose(...chain)(store.dispatch) // dispatch === store.dispatch
接下来考虑有一个中间件的情形,这种情况下直接返回中间件:
(next = store.dispatch) => (action) => { // next 就是 store.dispatch // ... 中间件对 action 进行处理 // 如果要交给原始的 dispatch 就调用 next };
下面我们来考虑最复杂的情形,即有多个中间件,这里 compose 将它们用 reduce 串接起来:
reduce
return funcs.reduce((a, b) => (...args) => a(b(...args)))
这样的写法可能具有误导性(让读者误以为 a 和 b 都是中间件),我们将形参改个名字理解起来会更容易:
a
b
return funcs.reduce( (alreadyChained, nextMiddleware) => (...args) => alreadyChained(nextMiddleware(...args)) )
可以看到最终形成的是一种链式调用,如果我们这样调用 compose 方法:
compose(a, b, c)
最终就会得到:
(...args) => a(b(c(...args)))
这样用户在调用 dispatch 的时候,中间件只要调用 next 就能将 action 抛给下一个中间件处理。
next
现在我们可以来探究用户调用 dispatch 时会发生什么了。我们已经知道了用户实际调用的是 compose 函数的返回值,所以实际上执行的是函数 [4],而我们知道在用 redux-thunk 的时候,action 里面会调用 disaptch,而这个 dispatch 就是 [4] 本身!只不过 [4] 第二次被调用的时候,走的是 next(action),而我们在上面分析过,这里 next === store.dispatch,这样就会到真正 Store 对象的 dispatch 方法啦。
[4]
next(action)
next === store.dispatch
下面这张图能够帮助你理解变量之间的关系:
combineReduces 也是个十分重要的函数,随着应用规模的扩大,你会希望不同的 reducer 能够处理状态树的局部而非一个 reducer 管理整个状态树。这个函数的代码在这里,其名十分贴切,工作原理就像 mapReduce。
combineReduces
首先这个方法会校验参数的正确性,然后返回一个名为 combination 的大 reducer。
combination
combination 一开始仍然是校验参数的正确性,真正执行 mapReduce 过程的只有这几行。可以看到它会分把先前状态的一部分摘取下来放到名为 previousStateForKey 的变量里,然后通过对应的 reducer 来产生局部的新状态,赋值到新的整体状态上,然后,判断局部的状态是否发生变化,从而判断整体的状态是否发生变化。
previousStateForKey
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 这里就不介绍了,感兴趣的话可以自行阅读其源码。
bindActionCreators
createStore
有 middleware 的情形后面再讨论,这里先讨论最简单的情形。
首先设置了初始状态,还有其他一些属性,通过闭包封装。
另外这个作用域内声明了这些函数:
reducer
在执行就返回当前状态树。dispatch
函数调用的时候会设置标志位isDispatching
为true
isPlainObject
方法确保传入的 action 是一个 PlainObject(原理是判断obj
的原型链上只有Object
),其他类型的 action 都交给中间件处理。currentReducer
来变更状态,变更后的状态会被赋值给currentState
属性。最终被封装在对象上返回,就是我们通常说的
Store
对象。Middleware
如果要使用中间件的话,在
createStore
的过程中,就会调用这一行代码:这里的 enhancer 实际上是
applyMiddleware
函数的返回值,接下来就看一看这个函数。redux-thunk 也算是官配了,而且它还非常简短,这里我们就拿它来举例子方便大家更好地理解:
使用这个 middleware 的时候如下所示:
我们现在开始仔细地分析调用过程:
applyMiddleware
函数接收多个 middleware,然后返回了一个函数B
A
中第一次调用的时候,createStore
方法被传递给函数B
,再次返回了一个函数C
,C
再被[A]
中的第二次调用所调用createStore
方法创建了一个真正的Store
对象。getState
和dispatch
这两个方法就被传给了函数1
,返回的是函数2
2
被compose
函数所用,生成了一个新的dispatch
方法3
,注意,这个方法是用户调用的dispatch
(它才是真身!)Store
对象,其实是个“套壳”的Store
我们先讲解
compose
方法,然后再来讲解用户调用dispatch
时会发生什么。compose
compose
函数负责将 dispatch 过程串起来。通过上面的分析我们已经知道了被送给compose
方法的是形如这样的一组函数:而
compose
的全部代码如下,它对 middleware 的数目分为三种情况来处理:我们先来考虑没有函数的情形,这种情况下返回一个透传函数
arg ⇒ arg
,即不对原始的 dispatch 做任何改变:接下来考虑有一个中间件的情形,这种情况下直接返回中间件:
下面我们来考虑最复杂的情形,即有多个中间件,这里
compose
将它们用reduce
串接起来:这样的写法可能具有误导性(让读者误以为
a
和b
都是中间件),我们将形参改个名字理解起来会更容易:可以看到最终形成的是一种链式调用,如果我们这样调用
compose
方法:最终就会得到:
这样用户在调用
dispatch
的时候,中间件只要调用next
就能将 action 抛给下一个中间件处理。用户调用 dispatch 时会发生什么
现在我们可以来探究用户调用
dispatch
时会发生什么了。我们已经知道了用户实际调用的是compose
函数的返回值,所以实际上执行的是函数[4]
,而我们知道在用 redux-thunk 的时候,action 里面会调用 disaptch,而这个 dispatch 就是[4]
本身!只不过[4]
第二次被调用的时候,走的是next(action)
,而我们在上面分析过,这里next === store.dispatch
,这样就会到真正Store
对象的dispatch
方法啦。下面这张图能够帮助你理解变量之间的关系:
combineReducers
combineReduces
也是个十分重要的函数,随着应用规模的扩大,你会希望不同的 reducer 能够处理状态树的局部而非一个 reducer 管理整个状态树。这个函数的代码在这里,其名十分贴切,工作原理就像 mapReduce。首先这个方法会校验参数的正确性,然后返回一个名为
combination
的大 reducer。combination
一开始仍然是校验参数的正确性,真正执行 mapReduce 过程的只有这几行。可以看到它会分把先前状态的一部分摘取下来放到名为previousStateForKey
的变量里,然后通过对应的 reducer 来产生局部的新状态,赋值到新的整体状态上,然后,判断局部的状态是否发生变化,从而判断整体的状态是否发生变化。总结
以上就是对 redux 源码的解读了。可以看到除了串联 middleware 的部分,都非常清晰易懂。对于 Redux 这样的库来说,其思想远比其实现精妙的多。
另外还有一个函数
bindActionCreators
这里就不介绍了,感兴趣的话可以自行阅读其源码。