likes
comments
collection
share

TypeScript 起死回生!Redux 5 官宣使用 TS 完全重构

作者站长头像
站长
· 阅读数 11

大家好,这里是大家的林语冰。

前阵子“Ruby On Rails”之父 D.H.H. 官宣 Turbo 8 弃用 TS,一时“大型项目弃用 TS”/“TS 已死”闹得沸沸扬扬。

TypeScript 起死回生!Redux 5 官宣使用 TS 完全重构

不久前,Redux 团队官宣 Redux 5 正式升级,亮点之一就是使用 TS 完全重构。就统计学意义而言,Redux 的 GitHub stars 高达 60_100(截至本文发布),比 up 主的女粉还多,大约是 Turbo 8 的 10 倍,重新定义了什么叫做“大型项目”。

TypeScript 起死回生!Redux 5 官宣使用 TS 完全重构

反正 up 主是“类型体操”都整不明白的 JS 爱好者,我们就先抛开 TS 不谈,来讲讲 Redux。

Redux 在前端生态的状态管理库领域赫赫有名或臭名昭著,有名当然是大数据显示其女粉超多,臭名源于一个悖论 —— 为什么 Redux 源码有且仅有几百行,而 Redux 相关文章却有一大坨,最骚的是,还没讲明白 Redux 是什么鬼物!

这是因为,Redux 大约是前端状态管理的“第一性原理”(更底层的可能是 Flux/Event Sourcing),所有后 Redux 时代的状态管理库,多多少少都受到其影响。

所以 Redux 是前端必知必会的一个库,但一大坨前端人却敬而远之,比如“我是 Vue 爱好者我不需要学 Redux”的思想钢印,其实来源于“Redux 能且仅能在 React 中使用”的认知偏差。

如果 Redux 能且仅能在 React 中使用,那 React-Redux 可以当场退役了。Redux 的“文艺复兴”可以让我们更愉快地玩弄 Vuex/Pinia、MobX/zustand/Jotai 等,这才是其精髓所在。

话说回来,以前 up 主的思想钢印是大型项目才需要 TS,现在 Redux 的源码有且仅有几百行,也使用 TS 重构,某种意义上也说明 TS/静态类型方案真正实现全民普及。

关于“Redux 的跨框架论文”和“reactive 之后不再需要 Pinia 的质疑”,我们日后再说。本期《前端翻译计划》共享的是 Redux 的版本升级公告,强烈建议和官方文档的迁移指南梦幻联动,以及关注 RTK 的进展。

免责声明

本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考,英文原味版请临幸 Redux v5.0.0

Redux 5 升级速览

TypeScript 起死回生!Redux 5 官宣使用 TS 完全重构

Redux 5 的重大升级包括但不限于:

  • 将代码库完全转换为 TS
  • 更新打包,更好地兼容 ESM/CJS,构建输出现代化
  • action.type 必须是字符串
  • 继续将 createStore 标记为已弃用
  • 弃用 AnyAction 类型,取而代之的是 UnknownAction 类型
  • 移除 PreloadedState 类型,取而代之的是 Reducer 类型的新泛型参数

此版本是所有 Redux 软件包主版本的一部分,涵盖破坏性更新

  • RTK(Redux Toolkit,工具集)2.0
  • Redux core(核心包)5.0
  • React-Redux 9.0
  • Reselect 5.0
  • Redux Thunk 3.0

Redux core、Reselect 和 Redux Thunk 作为 RTK 的一部分,RTK 用户无需手动升级它们 —— 您在升级到 RTK 2.0 时自动获取。(如果您尚未使用 RTK,请立即开始迁移现存的旧版 Redux 代码以使用 RTK!)

# RTK
pnpm add @reduxjs/toolkit

# 单独安装核心包
pnpm add redux

关于 TS 的变更

我们已经放弃了对 TS 4.6 及更早版本的支持,我们的支持矩阵现在是 TS 4.7+。

使用 TS 完全重构

2019 年,我们开始由社区驱动将 Redux 代码库转换为 TS。虽然但是,由于担心与现存生态系统可能存在兼容性问题(以及我们的普遍惯性),TS 转换的代码在存储库中放置了几年,未使用且未发布。

Redux core v5 现在是基于 TS 转换的源码构建。理论上,这在运行时行为和类型上应该与 Redux 4.x 构建几乎相同,但某些更改很可能会导致类型问题。

AnyAction 已弃用,取而代之的是 UnknownAction

Redux TS 类型始终导出 AnyAction 类型,该类型被定义为具有 {type: string} 并将任何其他字段视为 any。这使得编写诸如 console.log(action.whatever) 之类的用法变得很容易,但不幸的是,这没有提供任何有意义的类型安全。

我们现在导出 UnknownAction 类型,它将除 action.type 之外的所有字段视为 unknown。这鼓励用户编写类型守卫来检查 action 对象并断言其特定的 TS 类型。在这些检查中,您可以访问具有更好类型安全性的字段。

UnknownAction 现在是 Redux 源码中需要 action 对象的默认位置。

AnyAction 出于兼容性考虑仍然存在,但已被标记为已弃用。

请注意,RTK的 action 创建器有一个 .match() 方法,该方法充当有用的类型守卫:

if (todoAdded.match(someUnknownAction)) {
  // action 现在被类型注解为 PayloadAction<Todo>
}

您还可以使用新的 isAction 工具来检查未知值是否是某种 action 对象。

Middleware 类型已更改:中间件 actionnext 的类型为 unknown

以前,next 参数的类型为传递的 D 类型参数,action 的类型为从调度类型中提取的 Action。这些都不是一个安全的假设:

  • next 会被类型注解以获取所有调度扩展,包括链中较早且不再适用的扩展。
    • 就技术而言,next 类型注解为redux 基本库实现的默认 Dispatch 是最安全的,但这会导致 next(action) 出错(因为我们无法保证 action 实际上是一个 Action) —— 它不会考虑任何后续中间件,这些中间件除了在看到特定 action 时所给出的 action 之外返回任何内容。
  • action 不一定是已知的 action,它实际上可以是任何东西 - 举个栗子,thunk 是一个没有 .type 属性的函数(因此 AnyAction 是不准确的)

我们已将 next 更改为 (action: unknown) => unknown(这是准确的,我们不知道 next 期望或将返回什么),并更改了 action(如上所述,这是准确的)。

为了安全地与 action 参数内的值或访问字段进行交互,您必须首先执行类型守卫检查来缩小类型范围,比如 isAction(action)someActionCreator.match(action)

这个新类型与 Redux v4 Middleware 类型不兼容,因此如果包的中间件说它不兼容,请检查它从哪个版本的 Redux 获取类型!

移除 PreloadedState 类型,取而代之的是 Reducer 泛型

我们对 TS 类型进行了调整,以提高类型安全性和行为。

首先,Reducer 类型现在有一个 PreloadedState 可能的泛型:

type Reducer<S, A extends Action, PreloadedState = S> = (
  state: S | PreloadedState | undefined,
  action: A
) => S

为什么需要进行此更改?当 store 首次由 createStore/configureStore 创建时,初始状态设置为作为 preloadedState 参数(或若没有通过,则是 undefined)。这意味着,首次调用 reducer 时,会使用 preloadedState 进行调用。首次调用后,reducer 始终会传递当前状态(即 S)。

对于大多数普通的 reducerS | undefined 准确地描述了 preloadedState 可以传入的内容。虽然但是,combineReducers 函数允许预加载 Partial<S> | undefined 状态。

解决方案是使用一个单独的泛型来表示 reducer 接受的预加载状态。这样 createStore 就可以使用该泛型作为其 preloadedState 参数。

以前,这是由 $CombinedState 类型处理的,但这使事情复杂化,并导致了某些用户报告的问题。这完全消除了对 $CombinedState 的需要。

此更改确实涵盖某些破坏性更新,但总体而言不会对用户域中的用户升级产生巨大影响:

  • ReducerReducersMapObjectcreateStore/configureStore 类型/函数采用额外的 PreloadedState 泛型,默认为 S
  • 移除 combineReducers 的重载,取而代之的是采用 ReducersMapObject 作为其通用参数的单个函数定义。对于这些更改,移除重载是必要的,因为有时会选择错误的重载。
  • 明确列出 reducer 泛型的增强器将需要添加第三个泛型。

isAction 谓词

我们最近向 RTK 添加了一个 isAction 谓词,然后意识到它更适合 Redux 核心库。这可以用在任何有可能是 Redux action 对象的值的地方,并且您需要检查它是否实际上是一个 action。这对于与更新的 Redux 中间件 TS 类型一起使用特别有用,其中默认值现在是 unknown,并且您需要使用类型守卫来告诉 TS 当前值实际上是一个 action

Action 类型必须是字符串

我们始终明示用户 actionstate 必须是可序列化的,并且 action.type 应该是一个字符串。这既是为了确保 action 是可序列化的,也是为了在 Redux DevTools(开发者工具)中提供可读的 action 历史记录。

store.dispatch(action) 现在强行要求 action.type 必须是字符串,如果不是字符串就会报错,就像如果 action 不是普通对象一样,它会报错。

实际上,99.99% 的情况下这问题不大,且不会对用户(尤其是使用 RTK 和 createSlice 的用户)产生任何影响,但可能有某些旧版 Redux 代码库选择使用 Symbols 作为 action 的类型。

createStore 标记为已弃用

在 Redux 4.2.0 中,我们将原始的 createStore 方法标记为 @deprecated。严格而言,这不是一个破坏性更新,也不是 Redux 5.0 的新内容,但为了完整起见,我们在这里记录它。

此弃用只是一条视觉删除线,旨在鼓励用户将其 App 从旧版 Redux 模式迁移到现代 RTK API

弃用会导致导入和使用时出现视觉删除线,比如 createStore,但不会出现运行时错误或警告

createStore 将无期打工,且永远不会被移除。虽然但是,今天我们希望所有 Redux 用户都使用 RTK 来实现所有 Redux 逻辑。

要解决此问题,有以下三个选项:

  • 强烈建议切换到 RTK 和 configureStore
  • 当做无事发生。这只是一条视觉删除线,它不会影响代码的行为方式。请无视它。
  • 切换到使用现在导出的 legacy_createStore API,这是完全相同的函数,但没有 @deprecated 标记。最简单的选择是执行别名导入重命名,比如 import { legacy_createStore as createStore } from 'redux'

ESM/CJS 封装兼容性

Redux v5 和 RTK 2.0 版本的最大主题是尝试实现“真正的”ESM 包发布兼容性,同时仍支持已发布包中的 CJS。

主要构建产物现在是 ESM 文件 dist/redux.mjs。大多数构建工具应该能够识别这一点。还有一个 CJS 工件和名为 redux.legacy-esm.js 的 ESM 文件的第二副本,用于支持 Webpack 4(它无法识别 package.json 中的 exports 字段)。此外,所有构建产物现在都位于已发布包中的 ./dist/ 目录下。

现代化的构建输出

我们现在发布了针对 ES2020 的现代 JS 语法,包括但不限于 ?. 可选链运算符、{...} 对象扩展运算符和其他现代语法。

构建工具

我们现在正在使用 github.com/egoist/tsup 打包构建。我们现在还包括 ESM 和 CJS 产物的源码映射。

弃用 UMD 版本

Redux 始终附带 UMD 构建产物。这些主要用于作为脚本标签直接导入,比如在 CodePen 或无打包构建环境中。

我们已经从已发布的包中删除了这些构建产物,因为这些用例今天看起来相当罕见。

现在包中有一个 redux.browser.mjs 文件,可以从 CDN(比如 Unpkg)加载。

内部监听器实现

Redux store 始终使用数组来跟踪侦听器回调,并使用 listeners.findIndex 来移除取消订阅时的侦听器。正如我们在 React-Redux 中发现的那样,当许多侦听器同时取消订阅时,可能会出现性能问题。

在 React-Redux 中,我们使用更复杂的链表方法修复了这个问题。在这里,我们将 listeners 更新为存储在 Map 中,这比数组具有更好的移除性能。

实际上,这不应该产生任何实际效果,因为 React-Redux 在 <Provider> 中设置了订阅,并且所有嵌套组件都会订阅该订阅。虽然但是,很高兴在这里修复它。

我们还导出了 Redux 代码库中多年的 isPlainObject 工具。

您现在收看的是《前端翻译计划》,学废了的小伙伴可以订阅此专栏合集,我们每天佛系投稿,欢迎持续关注前端生态。谢谢大家的点赞,掰掰~

TypeScript 起死回生!Redux 5 官宣使用 TS 完全重构

转载自:https://juejin.cn/post/7311707089498390547
评论
请登录