2022年12月01日

TypeScript 4.9 中的 `satisfies` 如何满足你的 Prisma 工作流程

TypeScript 的新 satisfies 运算符允许一些新的、类型安全的模式,而这些模式以前需要冗长的类型注解或复杂的变通方法。本文将介绍几种使用它来帮助你完成常见 Prisma 相关工作流程的用例。

How TypeScript 4.9 `satisfies` Your Prisma Workflows

目录

一些背景知识

TypeScript 的优势之一在于它如何从上下文中 推断 表达式的类型。例如,你可以声明一个变量而不带类型注解,它的类型将从你赋给它的值中推断出来。当值的确切类型很复杂,并且显式注解类型会需要大量重复代码时,这尤其有用。

但是,有时显式类型注解很有用。它们可以帮助将代码的 意图 传达给其他开发者,并使 TypeScript 错误尽可能靠近错误的实际来源。

考虑一段代码,它定义了订阅定价层级,并使用 toFixed 方法在 Number 上将它们转换为字符串

如果我们对 plans 使用显式类型注解,我们可以更早地捕获拼写错误,并推断 users 参数的类型。然而,我们可能会遇到另一个问题

当我们使用显式类型注解时,类型会被“拓宽”,TypeScript 将无法再区分我们的哪些计划是固定价格,哪些是按用户收费。实际上,我们“丢失”了应用程序类型的某些信息。

我们真正需要的是一种方法,能够断言一个值与某个宽泛/可重用类型兼容,同时让 TypeScript 推断出更窄(更具体)的类型。

受限恒等函数

在 TypeScript 4.9 之前,解决这个问题的一个方法是使用一个 “受限恒等函数”。这是一个泛型的、空操作的函数,它接受一个参数和一个类型参数,以确保两者兼容。

这种函数的一个例子是 Prisma.validator 工具函数,它还做了一些额外的工作,只允许提供的泛型类型中定义的已知字段。

不幸的是,这个解决方案会产生一些运行时开销,仅仅是为了在编译时让 TypeScript 满意。一定有更好的方法!

介绍 satisfies

新的 satisfies 运算符提供了相同的优势,没有运行时开销,并且自动检查多余或拼错的属性。

让我们看看在 TypeScript 4.9 中,我们的定价层级示例可能是什么样子

现在我们直接在源头捕获了拼写错误,但我们没有因为类型拓宽而“丢失”任何信息。

本文的其余部分将介绍一些你可能在 Prisma 应用程序中使用 satisfies 的实际情况。

无需 Prisma.validator 即可推断 Prisma 输出类型

Prisma Client 使用泛型函数为你提供类型安全的结果。从客户端方法返回的数据的静态类型与你在查询中请求的形状相匹配。

当直接使用内联参数调用 Prisma 方法时,这非常有效

但是,你可能会遇到一些陷阱

  • 如果你尝试将查询参数分解成更小的对象,类型信息可能会“丢失”(拓宽),并且 Prisma 可能无法正确推断输出类型。
  • 很难获得表示特定查询输出的类型。

satisfies 运算符可以提供帮助。

推断 findManycreate 等方法的输出类型

satisfies 运算符在 Prisma 中最常见的用例之一是推断特定查询方法的返回类型,例如 findUnique,这只包含模型及其关系中选定的字段。

推断 count 方法的输出类型

Prisma Client 的 count 方法允许你添加一个 select 字段,以便计算指定字段非空值的行数。此方法的返回类型取决于你指定的字段

推断 aggregate 方法的输出类型

我们还可以获取更灵活的 aggregate 方法的输出形状,它允许我们获取各种模型字段的平均值、最小值、最大值和计数

推断 groupBy 方法的输出类型

groupBy 方法允许你对模型实例组执行聚合。结果将包括用于分组的字段以及聚合字段的结果。以下是你可以使用 satisfies 推断输出类型的方法

创建无损模式验证器

模式验证库(例如 zodsuperstruct)是运行时清理用户输入的好选择。其中一些库可以通过推断模式的静态类型来帮助你减少重复的类型定义。但是,有时你可能想为现有的 TypeScript 类型(例如 Prisma 生成的输入类型)创建一个模式验证器。

例如,在你的 Prisma schema 文件中,给定一个像这样的 Post 类型

Prisma 将生成以下 PostCreateInput 类型

如果你尝试使用 zod 创建一个与此类型匹配的 schema,你将“丢失”有关 schema 对象的一些信息

在 TypeScript 4.9 之前,一个变通方法是创建一个 schemaForType 函数(一种受限恒等函数)。现在有了 satisfies 运算符,你可以为一个现有类型创建 schema,而不会丢失有关该 schema 的任何信息。

以下是四种流行模式验证库的一些示例

定义可重用查询过滤器的集合

随着应用程序的增长,你可能会在许多查询中使用相同的过滤逻辑。你可能希望定义一些可重用并组合成更复杂查询的通用过滤器。

一些 ORM 内置了实现此功能的方法——例如,你可以在 Ruby on Rails 中定义 模型作用域,或者在 Django 中创建 自定义查询集方法

使用 Prisma,where 条件是对象字面量,可以使用 ANDORNOT 进行组合。 satisfies 运算符为我们提供了一种方便的方式来定义可重用过滤器的集合

带有推断返回类型的强类型函数

有时你可能希望断言一个函数与特定的函数签名匹配,例如 React 组件或 Remix loader 函数。在 Remix loaders 等情况下,你还希望 TypeScript 推断函数返回的具体形状。

在 TypeScript 4.9 之前,很难同时实现这两点。有了 satisfies 运算符,我们现在可以确保函数匹配特定的函数签名,而不会拓宽其返回类型。

让我们来看一个 Remix loader 的例子,它从 Prisma 返回一些数据

这里的 satisfies 运算符做了三件事

  • 确保我们的 loader 函数与 Remix 的 LoaderFunction 签名兼容
  • LoaderFunction 签名推断我们函数的参数类型,这样我们就无需手动注解它们
  • 推断我们的函数返回一个来自 Prisma 的 Post 对象,包括其相关的 comments

总结

TypeScript 和 Prisma 可以让你轻松在应用程序中实现类型安全的数据库访问。Prisma 的 API 旨在提供 零成本类型安全,这样在大多数情况下,你无需“选择加入”、用类型注解弄乱代码或提供泛型参数,就能自动获得强大的类型检查。

我们很高兴看到像 satisfies 运算符这样的新 TypeScript 功能如何在更高级的情况下帮助你获得更好的类型安全,同时将类型干扰降到最低。通过我们的 Twitter 联系我们,让我们知道你是如何使用 Prisma 和 TypeScript 4.9 的。

不要错过下一篇文章!

订阅 Prisma 新闻邮件