Redux 源码解析(一): createStore API

news/2024/7/15 19:33:23 标签: react.js, redux, store

Redux 源码解析(一): createStore API

文章目录

  • Redux 源码解析(一): createStore API
  • 前言
  • 正文
    • 0. 背景知识
      • 0.1 源代码版本:4.1.1, TS 实现
      • 0.2 源码目录结构
    • 1. createStore 源码解析
      • 1.1 导出类型
      • 1.2 状态 & 返回
      • 1.3 预处理
      • 1.4 获取状态 getState
      • 1.5 订阅状态 subscribe
      • 1.6 修改状态 dispatch
      • 1.7 置换核心函数 replaceReducer
  • 结语
  • 其他资源
    • 参考连接
    • 阅读笔记参考

前言

在 SPA 的前端架构底下,状态管理从来都是绕不开的话题。

本篇将要带大家来解析著名状态管理库 Redux 的源代码。与 Vuex 不同的是,Redux、MobX 这类的状态管理库是独立于任何前端框架的状态管理实现,例如 Redux 要利用 react-redux 作为桥梁才能更好地在 React 框架下使用。

Redux 的源码算是比较清晰容易理解的,为了充分理解状态管理的思想,同时更进一步的学习如何手写一个开源框架库,充分处理各种边界情况和编写容易使用的 api,我们的源码拆解将会分成更细的力度,细细品味每个部分的实现和思想

正文

本篇作为 Redux 源码解析的首篇,我们先来解析使用 Redux 创建 store 状态管理对象必用的 api 之一 - createStore

0. 背景知识

在开始之前我们先过一些基础的背景知识

0.1 源代码版本:4.1.1, TS 实现

由于 Redux 的 release 版本都是编译后的 js 文件,所以本篇使用 master 分支上的 4.1.1 版本的源代码,使用 TS 编写

0.2 源码目录结构

接下来是 Redux 源码的目录结构

redux-master/
└── src/
    ├── applyMiddleware.ts
    ├── bindActionCreators.ts
    ├── combineReducers.ts
    ├── compose.ts
    ├── createStore.ts
    ├── index.ts
    ├── types/
    │   ├── actions.ts
    │   ├── middleware.ts
    │   ├── reducers.ts
    │   └── store.ts
    └── utils/
        ├── actionTypes.ts
        ├── formatProdErrorMessage.ts
        ├── isPlainObject.ts
        ├── kindOf.ts
        ├── symbol-observable.ts
        └── warning.ts
  • types 目录下是各个 api 涉及的类型定义
  • utils 则是一些辅助的工具函数
  • 剩下位于 src 根目录的就是 redux 的几个主要的 api 实现

本篇主要介绍 createStore.ts 导出的 createStore API

1. createStore 源码解析

1.1 导出类型

首先我们先看看 createStore 导出的类型有哪些

  • /src/createStore.ts(源码笔记:/src/createStore.ts/exports.ts
import $$observable from './utils/symbol-observable'

import {
  Store,
  PreloadedState,
  StoreEnhancer,
  Dispatch,
  Observer,
  ExtendState
} from './types/store'
import { Action } from './types/actions'
import { Reducer } from './types/reducers'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
import { kindOf } from './utils/kindOf'

export default function createStore<S, A extends Action, Ext = {}, StateExt = never>(
  reducer: Reducer<S, A>,
  enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext;

export default function createStore<S, A extends Action, Ext = {}, StateExt = never>(
  reducer: Reducer<S, A>,
  preloadedState?: PreloadedState<S>,
  enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext;

我们看到实际上一共导出了两种 createStore 的方法签名重载,分别对应两个和三个参数的调用方式

1.2 状态 & 返回

接下来我们来看看 createStore 方法内定义的一些局部变量和返回对象

  • /src/createStore.ts(源码笔记:/src/createStore.ts/state.ts
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 {
  // ...

  let currentReducer = reducer
  let currentState = preloadedState as S
  let currentListeners: (() => void)[] | null = []
  let nextListeners = currentListeners
  let isDispatching = false

  // ...

  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
}

变量名称还是比较好懂的,一共定义了五个局部变量

  • currentReducer 保存当前的 reducer,为真正处理 action 并返回状态的核心函数
  • currentState 则是保存的状态本身
  • currentListenersnextListeners 则是将监听函数分成两个队列,在保留旧的队列的同时可以对新的队列进行预处理
  • isDispatching 标志则是规范在 redux 处理过程中必不可以同时请求状态,也就保证整个 redux 执行是同步不可中断的原子操作

最后我们看到返回的 store 对象总共给出了四个 api:

  • getState 获取当前状态
  • subscribe 订阅状态
  • dispatch 修改状态
  • replaceReducer 置换核心函数

1.3 预处理

接下来我们真正进入 createStore API 内部

  • /src/createStore.ts(源码笔记:/src/createStore.ts/precheck.ts

第一步骤当然是先对参数进行预处理,毕竟 js 实际上是不存在重载方法的,所以不同形式的调用必须自己处理输入参数才行

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 {
  // 传入参数校验
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.'
    )
  }

  // createStore(reducer, enhancer)
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error(
        `Expected the enhancer to be a function. Instead, received: '${kindOf(
          enhancer
        )}'`
      )
    }

    return enhancer(createStore)(
      reducer,
      preloadedState as PreloadedState<S>
    ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
  }

  if (typeof reducer !== 'function') {
    throw new Error(
      `Expected the root reducer to be a function. Instead, received: '${kindOf(
        reducer
      )}'`
    )
  }

  // ...

这部分还是比较简单的,读者自己看看

值得注意的是其中当存在 enhancer 的时候会直接返回调用的结果,这部分我们就不做探讨,让我们放到解析 applyMiddleware 的时候一起说明

1.4 获取状态 getState

接下来我们进入重头戏,返回的 store 对象上对应的 api

  • /src/createStore.ts(源码笔记:/src/createStore.ts/getState.ts

第一个是获取当前状态的方法,实际上就是一个 getter

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 {
  // ...

  let currentReducer = reducer
  let currentState = preloadedState as S
  let currentListeners: (() => void)[] | null = []
  let nextListeners = currentListeners
  let isDispatching = false

  /**
   * Reads the state tree managed by the store.
   *
   * @returns The current state tree of your application.
   */
  function getState(): S {
    if (isDispatching) {
      throw new Error(
        'You may not call store.getState() while the reducer is executing. ' +
          'The reducer has already received the state as an argument. ' +
          'Pass it down from the top reducer instead of reading it from the store.'
      )
    }

    return currentState as S
  }

  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
}

1.5 订阅状态 subscribe

第二个 api 则是用于订阅 reduxstore 状态

  • /src/createStore.ts(源码笔记:/src/createStore.ts/subscribe.ts)(篇幅有限省略一些类型检查的警告)
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 {
  // ...

  let currentReducer = reducer
  let currentState = preloadedState as S
  let currentListeners: (() => void)[] | null = []
  let nextListeners = currentListeners
  let isDispatching = false

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  function subscribe(listener: () => void) {
    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }
  
  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
}

在这里我们就能看出监听函数分成两个队列的用意,首先定义一个确保两个队列相互独立的方法

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

接下来订阅的时候就是将监听函数放入新队列当中

  function subscribe(listener: () => void) {
    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

然后最后返回能够解除订阅的方法

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }

1.6 修改状态 dispatch

接下来就是 Redux 的重头戏,用于修改状态的 dispatch 函数

  • /src/createStore.ts(源码笔记:/src/createStore.ts/dispatch.ts)(一样去除一些不必要的警告和检查)
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 {
  // ...

  let currentReducer = reducer
  let currentState = preloadedState as S
  let currentListeners: (() => void)[] | null = []
  let nextListeners = currentListeners
  let isDispatching = false

  function dispatch(action: A) {
    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
  }

  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
}

dispatch 的核心在于将前一次的状态和 action 作为参数传入 reducer 来获取下一次的状态

    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

值得注意的有两个点

  1. (currentListeners = nextListeners) 使得我们在真正 dispatch 的时候才会去使用新的队列,使得我们在操作 subscribe/unsubscribe 的时候能更直接的操作 nextListeners 而避免对 currentListeners 有过多过于复杂的修改
  2. 返回 action 的目的是在于配合中间件的调用模式,保留 action 并继续向下传递

1.7 置换核心函数 replaceReducer

最后一个 api 就是用于置换 reducer 的方法

  • /src/createStore.ts(源码笔记:/src/createStore.ts/replaceReducer.ts

reducer 函数就好像整个 store 对象的命脉,决定了往后所有状态的变动的核心函数

不过 replaceReducer 方法的实现也异常简单

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 {
  // ...

  let currentReducer = reducer
  let currentState = preloadedState as S
  let currentListeners: (() => void)[] | null = []
  let nextListeners = currentListeners
  let isDispatching = false

  function replaceReducer<NewState, NewActions extends A>(
    nextReducer: Reducer<NewState, NewActions>
  ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {
    // TODO: do this more elegantly
    ;(currentReducer as unknown as Reducer<NewState, NewActions>) = nextReducer

    dispatch({ type: ActionTypes.REPLACE } as A)
    // change the type of the store by casting it to the new store
    return store as unknown as Store<
      ExtendState<NewState, StateExt>,
      NewActions,
      StateExt,
      Ext
    > &
      Ext
  }

  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
}

其实就是将新的 reducer 保存

    ;(currentReducer as unknown as Reducer<NewState, NewActions>) = nextReducer

接下来再重新调用一个 dispatch 来对新的 reducer 进行初始化

    dispatch({ type: ActionTypes.REPLACE } as A)
    // change the type of the store by casting it to the new store
    return store as unknown as Store<
      ExtendState<NewState, StateExt>,
      NewActions,
      StateExt,
      Ext
    > &
      Ext

结语

本篇对于 Redux 的源码解析就先在这里告一段落,鉴于上次解析 Vue2 的时候产生超过 2000 行的长篇大论,我认为应该将整个源码进行适当的拆解之后逐个击破才是正确的攻略方式。

本篇介绍的 createStore 方法,相信大家都了解 store 对象究竟是怎么来的啦,同时也留下了许多谜团

  • reducer 又有普通 reducer,还可以 combineReducer => 留到 combineReducers API 的篇幅再说明
  • dispatch 的 middleware 模式又是如何实现的呢?这个则留到 applyMiddleware API 的部分来讲解
  • 还有就是 action 的用法,我们常常看到会写成 actionCreator 的形式(这也是官方推荐的写法),这部分则留到 bindActionCreators API 的部分来讲解。

希望本篇能帮助大家更容易的理解 redux 源码

其他资源

参考连接

TitleLink
redux - Githubhttps://github.com/reduxjs/redux

阅读笔记参考

https://github.com/superfreeeee/Blog-code/tree/main/source_code_research/redux-4.1.1


http://www.niftyadmin.cn/n/735219.html

相关文章

华三路由_BGP技术详解(H3C)

1.BGP的基本概念1&#xff09;BGP &#xff08; Border Gateway Protocol&#xff0c;边界网关协议&#xff09;是一种既可以用于不同 AS &#xff08; Autonomous System&#xff0c;自治系统&#xff09;之间&#xff0c;又可以用于同一 AS 内部的动态路由协议。当 BGP 运行于…

Redux 源码解析(二): bindActionCreators combineReducers API

Redux 源码解析(二): bindActionCreators & combineReducers API 文章目录Redux 源码解析(二): bindActionCreators & combineReducers API前言正文1. bindActionCreators 源码解析1.1 类型定义1.2 导出方法签名1.3 源码实现2. combineReducers 源码解析2.1 使用背景2.…

学习笔记:Linux实时同步软件之inotify

Linux 内核从 2.6.13 版本开始提供了 inotify 通知接口&#xff0c;用来监控文件系统的各种变化情况&#xff0c;如文件存取、删除、移动等。利用这一机制&#xff0c;可以非常方便地实现文件异动告警、增量备份&#xff0c;并针对目录或文件的变化及时作出响应。inotify可以通…

Redux 源码解析(三): compose applyMiddleware API

Redux 源码解析(三): compose & applyMiddleware API 文章目录Redux 源码解析(三): compose & applyMiddleware API前言正文1. compose 源码解析1.1 方法签名1.2 源码实现2. applyMiddleware 源码解析2.1 类型定义2.2 方法签名2.3 源码实现结语其他资源参考连接阅读笔记…

超简单将Centos的yum源更换为国内的阿里云源

自己的yum源不知道什么时候给改毁了……搜到了个超简单的方法将yum源更换为阿里的源 完全参考 http://mirrors.aliyun.com/help/centos?spm5176.bbsr150321.0.0.d6ykiD 1、备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2、下载新的Ce…

ztree 模糊搜索数据重复问题处理

var btnName"station"var treeObj$.fn.zTree.getZTreeObj(btnName"Tree"); nodeListtreeObj.getNodesByParamFuzzy("pinyin", $btn.val().trim().toLowerCase(),null);//移除重复站点信息 nodeListremoveNodes(nodeList);/** * 不清楚是不是ztre…

技术方案实践: 前端轮询方案实现 思考

技术方案实践: 前端轮询方案实现 & 思考 文章目录技术方案实践: 前端轮询方案实现 & 思考前言正文0. 什么叫轮询&#xff1f;1. 轮询接口定义 & 数据结构2. 轮询方案 1: 使用定时器3. 轮询方案 2: 使用尾递归3.1 增加取消机制3.2 增加组件卸载时终止轮询机制3.3 防…

解决:make:cc 命令未找到的解决方法

安装Redis的时候报这个错误 原因&#xff1a;未安装gcc 解决方法&#xff1a;安装gcc 自动安装&#xff0c;包括依赖库[rootVM_220_111_centos redis-3.2.9]# yum -y install gcc automake autoconf libtool make