👉 引言:这是一个源码共读的系列文章,我管它叫偷裤衩,顾名思义,非常形象,妙不可言,不可多言,回味无穷。
废话不多说,我们直接从项目入口开始,Redux 的 Repo 不算复杂,就不详细介绍了。
直接来到 src/index.ts
,我过滤掉了一些导出的类型模块。
可以从导出的一些模块中,看到Redux
的一些大概功能,这次我们会主要看createStore
模块。
下面代码,我们去到createStore.ts
,我删掉了一些跟主逻辑无关的代码,比如:if
判断分支,大部分是为了抛出错误。(和主逻辑无关,并不代表不重要,反而在正常编码中非常依赖这些if
分支给程序做鲁棒性的建设,只是这里我们不用关注)
createStore
函数内部有以下6个变量定义:
let currentReducer = reducer
let currentState
let currentListeners = new Map()
let nextListeners = currentListeners
let listenerIdCounter = 0
let isDispatching = false
createStore
函数内部有以下6个方法定义:
function ensureCanMutateNextListeners
function getState
function subscribe
function dispatch
function replaceReducer
function observable
createStore
函数最终导出一个store
对象:
```ts:line-numbers {2,3,4,5} const store = { dispatch: dispatch as Dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } as unknown as Store<S, A, StateExt> & Ext return store
#### 事实上的内容2️⃣
事实上我们只需要搞清楚这一个函数是怎么运行的,也就差不多搞懂了 `redux` 的核心之一,你没有看错,`redux` 的 `createStore` 就是这么简洁高效。
#### 深入理解3️⃣
```ts:line-numbers {1}
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) { // [!code focus]
nextListeners = new Map() // [!code focus]
currentListeners.forEach((listener, key) => {
nextListeners.set(key, listener)
})
}
}
::: details 点我查看ensureCanMutateNextListeners
定义的解读🔝
可以看到ensureCanMutateNextListeners
函数内部的if
分支当currentListeners
和nextListeners
严格相等时会执行,而if
代码执行后nextListeners = new Map()
,然后把当前的Listeners
都添加到里面去,后续在dispatch
方法那里会被赋值currentListeners = nextListeners
,也就是把下一个监听者复制到当前监听者,因为在dispatch
的时候也就会调用listeners
里面的每一个listener
方法,也就是告诉每一个监听者内部状态的变化。
:::
```ts:line-numbers {1} function subscribe(listener: () => void) { let isSubscribed = true
ensureCanMutateNextListeners() // [!code focus] const listenerId = listenerIdCounter++ nextListeners.set(listenerId, listener)
return function unsubscribe() {
isSubscribed = false
ensureCanMutateNextListeners() // [!code focus]
nextListeners.delete(listenerId)
currentListeners = null } } ```
::: details 点我查看subscribe
定义,ensureCanMutateNextListeners
调用的解读🔝
而该函数的调用是在subscribe
函数内,和subscribe
函数返回值unsubscribe
函数内分别被调用了一次,就如同它的函数名一样,只是为了确保可以修改nextListeners
这个变量,因为在subscribe
调用时需要把传入的listener
加入到nextListeners
里面去,而在unsubscribe
里要进行删除对应的listener
的操作,这里可以注意一下,巧妙的利用闭包,实现了unsubscribe
函数,真是无处不在的闭包啊。
:::
```ts:line-numbers {1} function getState(): S { /* 我知道这个函数有点脱裤子放屁,
::: details 点我查看getState
定义的解读🔝
这个getStatue
相对简单,主要是为了安全返回currentState
。
:::
function observable() {
const outerSubscribe = subscribe // [!code focus]
return {
subscribe(observer: unknown) {
function observeState() {
const observerAsObserver = observer as Observer<S>
if (observerAsObserver.next) {
observerAsObserver.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState) // [!code focus]
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
::: details 点我查看observable
定义的解读🔝
observable
函数把上下文中的subscribe
函数在其内部rename,然后返回了一个包含一个新的subscribe
方法的对象,这个方法接受一个观察者observer
,又在方法内部定义了一个observerState
的函数并执行,该函数实际上就是为了在你订阅(调用subscribe
)的时候,主动触发一次观察者(observer
)的next
方法,并传入当前上下文中的currentState
,总结来说也就是订阅的时候就获取一次当前的state,最后利用outerSubscribe
(上下文中的subscribe
)真正完成订阅这个操作,最后通过引用传递导出取消订阅的函数,外部用的,其实就是observable
函数导出的这个对象。
:::
function dispatch(action: A) {
try {
isDispatching = true
currentState = currentReducer(currentState, action) // [!code focus]
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners) // [!code focus]
listeners.forEach(listener => {
listener() // [!code focus]
})
return action
}
::: details 点我查看dispatch
定义的解读🔝
dispatch
这里首先会尝试调用currentReducer
(createStore
传入的第一个参数),并且传入当前的状态和操作类型(currentState
和调用dispatch
时传入的action
),然后把currentReducer
的返回值赋值给当前的状态(currentState
),接着定义了listeners
,把nextListeners
赋值给currentListeners
和listeners
,然后遍历触发listener
通知状态更新(这里有发布订阅的思想)。
:::
function replaceReducer(nextReducer: Reducer<S, A>): void {
currentReducer = nextReducer as unknown as Reducer<S, A, PreloadedState>
dispatch({ type: ActionTypes.REPLACE } as A)
}
::: details 点我查看replaceReducer
定义的解读🔝
replaceReducer
函数的逻辑也不复杂,主要是为了替换当前的reducer,并且在把传入的reducer(函数传入的nextReducer
)赋值给当前上下文中的reducer(原本createStore
调用时传入的reducer),然后手动触发一次dispatch
方法,以触发更新状态和通知观察者的整个逻辑。
:::
以上就是createStore
的主要逻辑了,可以看到整个的实现充斥了非常多的闭包,scope chain
等js
特性的利用,设计模式主要是观察者模式(subscribe
函数,以及observable
函数)和发布订阅模式(dispatch
中的forEach
调用,派发更新通知),当然了,Redux
的核心模块不止createStore
,还有中间件的思想(applyMiddleware
),还有合并reducer等。我们下次继续吧~
🖥️写在最后:
以上就是这期【偷裤衩】的全部内容了,阅读源码就像是读书,沿着各个源码作者的编码思路进行探索的过程,这有助于帮助自己偷师百家,成为仙道巅峰之人。