回忆一下我们使用 redux
的流程,首先,我们从 Redux
中引入 createStore
方法,然后调用 createStore
方法,并将 Reducer
作为参数传入,用来生成 Store
。为了接收到对应的 State
更新,我们先执行 Store
的 subscribe
方法,将render
作为监听函数传入。然后我们就可以 dispatch action
了,对应更新 view
的 State
。
可以看出应用的入口就是这个 createStore
函数,createStore
主要用于 Store
的生成,我们就从它开始阅读,已进入函数就是又臭又长的重载函数,函数前半段就是对参数的判断,我们就挑参数最全的入口来看并且忽略掉参数判断部分:
export default function createStore<
S,
A extends Action,
Ext = {},
StateExt = never
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {
// ...
}
抛开参数处理,我们可以看到声明了一系列函数,然后执行了dispatch
方法,最后暴露了dispatch
、subscribe
……几个方法。这里dispatch
了一个init Action
是为了生成初始的State
树。
export default function createStore(reducer, preloadedState, enhancer) {
let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
let isDispatching = false
function getState(): S { /** some code */ }
function subscribe(listener: () => void) { /** some code */ }
function dispatch(action: A) { /** some code */ }
function replaceReducer<NewState, NewActions extends A>(
nextReducer: Reducer<NewState, NewActions>
): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext { /** some code */ }
function observable() { /** some code */ }
dispatch({ type: ActionTypes.INIT } as A)
const store = ({
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
} as unknown) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
return store
}
ActionTypes.INIT
和 REPLACE
是 redux
内置的 action
,在初始化和更换 reducer
的时候触发,执行的效果就是用传入的 preloadState
初始化一下状态树,然后执行一下订阅函数。
const randomString = () =>
Math.random().toString(36).substring(7).split('').join('.')
const ActionTypes = {
INIT: `@@redux/INIT${/* #__PURE__ */ randomString()}`,
REPLACE: `@@redux/REPLACE${/* #__PURE__ */ randomString()}`,
PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}
我们先挑两个简单的函数看下,getState
和 replaceReducer
,其中 getState
只是返回了当前的状态。replaceReducer
是替换了当前的 Reducer
并重新初始化了 State
树。这两个方法比较简单:
replaceReducer
replaceReducer
更改了 currentReducer
缓存的 reducer
,通过 ActionTypes.REPLACE
来初始化最后返回 store
:
export default function createStore(reducer, preloadedState, enhancer) {
let currentReducer = reducer
// ...
function replaceReducer<NewState, NewActions extends A>(
nextReducer: Reducer<NewState, NewActions>
): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {
// 参数判断
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
// 替换 reducer
;((currentReducer as unknown) as Reducer<
NewState,
NewActions
>) = nextReducer
// ActionTypes.REPLACE 和 INIT 的影响是一致的,都会初始化 reducer tree
dispatch({ type: ActionTypes.REPLACE } as A)
return (store as unknown) as Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext
}
getState
getState
就太简单了,currentState
是通过闭包缓存的 state
数据,上面我们说过 redux
只有一个单一的状态树,所以返回的时候也只会返回一个 state
:
export default function createStore(reducer, preloadedState, enhancer) {
let currentState = preloadedState as S
//...
function getState(): S {
if (isDispatching) {
throw new Error(/** long error msg */)
}
return currentState as S
}
}
这里 dispatch
的过程中不能发生 getState
道理也很简单,就相当于是一个读写锁的机制,读数据的时候其他人也可以读而也可以写,写数据的不论是写还是读都不可以发生。
后面的 subscriber
可以看作和 getState
一个性质作为读行为。
dispatch
store.dispatch(action)
是唯一的派发 action
来更改 state
状态的函数,先来看看它参数检测的部分,首先 action
必须要有 type
属性:
function dispatch(action: A) {
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?'
)
}
// ...
}
其次就是检测 action
是否是一个 plainObject
,即该对象由 Object
构造函数创建,或者 [[Prototype]]
为 null
,这样是为了防止副作用的发生,我们可以看看 isPlainObject
这个检测函数:
通过一个 while
循环拿到参数对象原型链的最后一个值,然后和对象原型链上的第一个值比较
如果对象是一个 PlainObject
那最后一个和第一个都是 Object.prototype
,注意这里还少了一个 edge case
的判断,即 obj
由 Object.create(null)
构建的时候,按道理说这里应该允许这种
function isPlainObject(obj: any): boolean {
if (typeof obj !== 'object' || obj === null) return false
let proto = obj
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto
}
接下来我们回到 dispatch
这个函数,首先同一时间只能有一个 dipatch
出发,不能并发的对 store
存储进行更新,会造成更新丢失,之后我们通过 currentReducer(currentState, action)
更新 currentState
,最后执行订阅的 listener
更新推送到视图或者其他订阅者:
export default function createStore(reducer, preloadedState, enhancer) {
let isDispatching = false
let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
function dispatch(action: A) {
// ...
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
}
}
subscribe
订阅函数subscribe
的主要作用是注册监听事件,然后返回取消订阅的函数,它把所有的订阅函数统一放一个数组里,只维护这个数组。在 react
中这个订阅函数通常就是 () => ReactDom.render(<App />)
这样一个渲染函数:
下面来解释一下为什么我们有两个监听队列 currentListeners
和 nextListeners
,这是为了做到实时性,首先在 dispatch
后半部分我们是可以触发订阅的,比如上面的 dispatch
执行到执行 listener
监听者函数的时候发生了添加订阅的行为,那么 redux
的实现原则如下:
\1. 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 thedispatch()
that is currently in progress. However, the nextdispatch()
call, whether nested or not, will use a more recent snapshot of the subscription list.
dispatch
执行的一定是 currentListeners
的快照,新添加的订阅函数不会被执行,而下一次 dispatch
一定会执行新添加的订阅函数。所以说我们有两个订阅队列,每次 subscribe
更新的是 nextListeners
,而 dispatch
执行函数前获取的是当时最新的队列 const listeners = (currentListeners = nextListeners)
每次准备更新 nextListeners
的时候都产生一个新的切片防止其对 currentListener
的影响:
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
了解实时性的背后原理之后,subscribe
所做的就只是向 nextListeners
推入一个监听函数然后返回一个取消订阅的函数:
export default function createStore(reducer, preloadedState, enhancer) {
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
let isDispatching = false
function subscribe(listener: () => void) {
// 参数判断
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 检测 dispatch 状态,如果正在 dispatch 的状态中则不能添加订阅
if (isDispatching) {
throw new Error(/** long error msg */)
}
ensureCanMutateNextListeners()
// 两个队列
nextListeners.push(listener)
// 开启订阅
let isSubscribed = true
return function unsubscribe() {
if (!isSubscribed) return
if (isDispatching) throw new Error(/** long error msg */)
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
}