Vesper@之一

【偷裤衩】Dan佬的Redux😎

👉 引言:这是一个源码共读的系列文章,我管它叫偷裤衩,顾名思义,非常形象,妙不可言,不可多言,回味无穷。

  1. 简单聊一下【偷裤衩】的价值:
  1. 简单聊一下【偷裤衩】的步骤:

开始🚀

废话不多说,我们直接从项目入口开始,Redux 的 Repo 不算复杂,就不详细介绍了。

直接来到 src/index.ts,我过滤掉了一些导出的类型模块。

code1

可以从导出的一些模块中,看到Redux的一些大概功能,这次我们会主要看createStore模块。

code2

下面代码,我们去到createStore.ts,我删掉了一些跟主逻辑无关的代码,比如:if判断分支,大部分是为了抛出错误。(和主逻辑无关,并不代表不重要,反而在正常编码中非常依赖这些if分支给程序做鲁棒性的建设,只是这里我们不用关注)

code3

简单的思维拆解1️⃣

createStore函数内部有以下6个变量定义:

createStore函数内部有以下6个方法定义:

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分支当currentListenersnextListeners严格相等时会执行,而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这里首先会尝试调用currentReducercreateStore传入的第一个参数),并且传入当前的状态和操作类型(currentState和调用dispatch时传入的action),然后把currentReducer的返回值赋值给当前的状态(currentState),接着定义了listeners,把nextListeners赋值给currentListenerslisteners,然后遍历触发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 chainjs特性的利用,设计模式主要是观察者模式(subscribe函数,以及observable函数)和发布订阅模式(dispatch中的forEach调用,派发更新通知),当然了,Redux的核心模块不止createStore,还有中间件的思想(applyMiddleware),还有合并reducer等。我们下次继续吧~


🖥️写在最后:

以上就是这期【偷裤衩】的全部内容了,阅读源码就像是读书,沿着各个源码作者的编码思路进行探索的过程,这有助于帮助自己偷师百家,成为仙道巅峰之人。