使用react
+redux
开发有一段时间了,刚开始使用并没有深入了解其源码,最近静下心来,阅读了一下,感触颇深。
本系列主要从createStore
,combineReducer
,compose
,applyMiddleware
几个方面进行讲解。本系列不会详细讲解redux
的用法,因此可能更适合有redux
使用经验的小伙伴们阅读。
store
是redux
中用来存储所有state
树的一个对象,改变store
内的状态的唯一方法时对它dispatch
一个action
。
redux
中通过createStore
创建一个store
,用来存放应用中所有状态树,一个应用中应该有且只有一个store
。
首先,我们先来回顾一下createStore
的用法:
用法:
createStore(reducer, [preloadedState], enhancer)
源码标注解读
import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'
export const ActionTypes = {
INIT: '@@redux/INIT'
}
export default function createStore(reducer, preloadedState, enhancer) {
//preloadedState是可选的
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
//通过`enhancer`重新构建一个增强过的`store`。
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
/**
* 订阅器在每次`dispatch`调用之前都会保存一份快照。当你正在调用`listener`的时候,`subscribe`或者`unSubscribe`,
* 对当前的`dispatch`都不会有影响。
* 但是,对于下一次`dispatch`,不管是否嵌套,都会使用`subscribe`列表中最近的一次快照。
*/
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function getState() {
return currentState
}
//注册一个`change listener`,每当`dispatch`一个`action`的时候就会触发这个`listener`。
//同时,返回一个`unsubscribe`函数,用来取消注册监听函数。
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
//未注册 ,返回
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
//从`listener list`中剔除当前`listener`。
nextListeners.splice(index, 1)
}
}
//分发`action`,这是改变`store`中`state`的唯一方式。
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
//使用`getState`的结果和传入的`action`以同步的方式调用`store`的`reduce`函数,返回值会被作为下一个`state`.
//currentReducer ==> rootReducer
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
//确保即使在 `listener`中进行`subscribe`或者`unSubscribe`,对当前的`dispatch`也不会有任何的影响。
const listeners = currentListeners = nextListeners
//遍历调用各个`listener`
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
//替换 `store` 当前用来计算 `state` 的 `reducer`
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer) {
if (typeof observer !== 'object') {
throw new TypeError('Expected the observer to be an object.')
}
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
针对,subscribe
和dispatch
这两个方法,有几点需要注意的地方:
1.虽然在注册的listener
里面调用dispatch
在技术上可行的,但是由于每一次dispatch
一个action
的时候都会造成所有listener
的执行,而listener
在执行的时候又会进行dispatch
的调用,这样可能会陷入一个无穷的循环当中。
2.关于函数ensureCanMutateNextListeners
的作用。我在刚开始看redux
的时候,曾一度很纠结ensureCanMutateNextListeners
这个函数的作用,感觉明明可以直接用currentListeners
的,为什么非得多此一举把它进行一份拷贝呢?
后来,在阅读了redux
的api
之后,发现了这样一句话。
The subscriptions are snapshotted just before every dispatch() call. If you subscribe or unsubscribe while the listeners are being invoked, this will not have any effect on the dispatch() that is currently in progress. However, the next dispatch() call, whether nested or not, will use a more recent snapshot of the subscription list.
大概翻译一下,就是说:
订阅器(subscriptions) 在每次 dispatch() 调用之前都会保存一份快照。当你在正在调用监听器(listener)的时候订阅(subscribe)或者去掉订阅(unsubscribe),对当前的 dispatch() 不会有任何影响。但是对于下一次的 dispatch(),无论嵌套与否,都会使用订阅列表里最近的一次快照。
可能还有点迷茫,那我们来举个例子说明一下:
下面是目前redux dispatch
的源码:
function subscribe(listener){
...
ensureCanMutateNextListeners()
nextListeners.push(listener)
...
}
function dispatch(action){
...
const listeners = currentListeners = nextListeners
//确保即使在 listener中进行subscribe或者unSubscribe,对当前的dispatch也不会有任何的影响。
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
...
}
假如我们不使用nextListeners
这个快照的话,那么代码会变成下面这个样子:
function subscribe(listener){
...
currentListeners.push(listener)
...
}
function dispatch(action){
...
for (let i = 0; i < currentListeners.length; i++) {
const listener = currentListeners[i]
listener()
}
...
}
注意到什么了吗?还没有?好,那我们继续往下看:
假如,我们设置了这样一个listener
:
function listenerA(){
store.subscribe(listenerA);
}
那么,当我们dispatch
一个action
之后,会发生什么?
首先,它会触发所有的listener
,假设我们当前就只有这么一个。那么currentListeners.length===1
,然后在执行这个listener
的时候,它又会绑定一个新的listener
。这时候currentListeners.length===2
,for
循环会继续执行下一个listener
,然后currentListeners.length===3
…这样,就会陷入一个死循环。
当我们使用了nextListeners
这个快照之后,情况又如何呢?
同样,当我们dispatch
一个action
之后,它会触发所有的listener
,然后在执行这个listener
的时候,又会绑定一个新的listener
。好,到此一次循环结束。这时候来看一下listeners.length
的值,因为有了nextListeners
这个快照的存在,listeners.length
的值还是1。不管我们在listener
中做什么样的操作,subscribe
还是unsubscribe
,对当前的dispatch
都是没有影响的。
而下一次的listeners
又是最近一次listener list
的快照的值。