你可能误会了!用 TypeScript 的正确姿势并不是这样子的
照片由 Ash Edmonds 拍摄,来自 Unsplash
简而言之: 在运行时必须验证外部数据。
如果你有一些 Web 开发的经验,你肯定遇到过在使用来自 API 的外部数据时出现的运行时错误。使用 TypeScript 可以显著减少这些错误,因为它会提醒你在整个应用程序中任何数据的结构和类型。虽然 TypeScript 在编译期间能有效防止已知数据的不可能操作,但在处理外部(换句话说,未知的)数据时,TypeScript 可能过于宽松。
在这篇文章里,我将解释为什么TypeScript允许编写可能出错的代码,以及如何防止这些数据相关的错误。
TypeScript的目的正如我在介绍中所说,TypeScript 的理念是追踪整个代码中任何数据的结构和类型。这不仅有助于提供 IDE 中的代码补全功能,还可以防止无效操作导致运行时错误。理论上,所有可能的运行时错误都可以在 TypeScript 编译过程中预测和识别。但是实际情况并非如此。
TypeScript偏离轨道了吗?实际上,TypeScript的主要目标是提升生产效率。也就是说,TypeScript更看重生产效率而非“安全性”。
一个很好的例子是类型any
。虽然它存在,但大家普遍认为不应该使用它。然而,不在代码中使用any
并不意味着我们的应用程序就完全避免了运行时错误。请看下面的代码片段:
const 显然是文章: /* 文章类型 */ = JSON.parse(input); // input 是字符串
因为 JSON.parse
的返回类型是 any
,它可以与一个明确类型声明的变量关联(例如这里的 Article
)。不手动写 any
,我们就让 TypeScript 忽略运行时可能出现的类型不匹配问题,即解析的内容不符合 Article
类型的要求。
我们得记住,
任何
_经常被用来表示任何`在外部定义文件中(这看起来不会改变),所以我们必须更加小心。
未知与断言
如果使用的是 unknown
而不是 any
,上面的代码片段将无法工作。我们就得用 as
关键字来写明确的 断言 :
// 将输入解析为JSON并断言其为Article对象
const shouldBeAnArticle = JSON.parse(input) as Article;
我们明确告诉 TypeScript 使用这种语法来放宽限制。虽然还是不行,但至少不再藏着了!
类型窄化的表达式与其依赖不安全的类型断言,我们可以使用类型细化表达式。
“将类型精简为比原先声明的类型更具体的过程称为窄化” — TypeScript 官方文档
JavaScript 提供的 typeof
操作符可以在运行时确定该对象的类型。
console.log(typeof 42);
// 预期输出: "number"
在条件中使用时,TypeScript 可以缩小对象的类型。
if (typeof input === "string") {
submit(input.toLowerCase());
}
这个表达式允许 TypeScript 推断出在这个作用域里 input
只能是一个字符串。
歧视行为断言语句让TypeScript信任开发者,而类型窄化表达式则是从运行时逻辑中推断类型
虽然 TypeScript 可以用许多其他表达式来缩小类型范围,这种情况仅在处理联合类型或基本类型时才有意义。我称之为“类型区分法”。
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) {
// 就可以调用游泳方法了,
return animal.swim();
}
// 就可以调用飞翔方法了,
return animal.fly();
}
通过如下的例子,关键字 in
允许 TypeScript 判断 animal
对象。
当数据为 unkown
(未知的)时,类型判断会浪费时间:
if (typeof input !== "string") {
// 输入还是未知的
}
这意味着我们不能仅仅依赖类型过滤表达式来处理外部数据的类型,而是需要另一种方式:数据验证。
زود来了,来救场Zod 本质上是一个对象模式验证库。这意味着它可以在运行时验证任何具有定义的模式的对象的有效性。
定义架构第一步是为Zod设定架构。
导入 * as z from "zod";
const userSchema = z.object({
id: z.number(),
name: z.string(),
age: z.number().可选()
}).严格模式();
如果你之前使用过其他验证工具,比如yup 或 joi,你可能已经熟悉这种做法。Zod 提供了多个函数,例如 object()
,string()
,每个函数都返回一个 Zod 架构,这些架构可以组合在一起,形成更大的架构。
每个模式定义都可以通过像 .optional()
这样的方法来“细化”,以此来获得复杂的验证规则。
使用由于 Zod 的流行,你可以找到许多工具来帮助你将现有的类型和接口转换为 Zod 架构定义。我还喜欢transform.tools网站,可以快速将一个 JSON 文件转换为 Zod 架构。
如何使用
这个模式基本上提供了两种验证数据的方式。一个是可能会抛出错误的 .parse()
方法,以及 .safeParse()
方法。如下:
const result = userSchema.safeParse(input);
if (!result.success) {
result.error
} else {
result.data // 这里的数据类型会根据 userSchema 来推断
}
要么解析失败,要么解析返回与定义的验证模式匹配的对象。在这种情况下,该对象会获得根据模式结构推断出的类型。
根据模式中推断类型信息通常,数据通常会在多个作用域和上下文中被共享。出于这个原因,我们通常只声明一次类型别名,然后在任何使用该数据的地方使用它。Zod 提供了 z.infer<>
来获取从模式推导出的类型。
类型 Article 等于 z 库中根据 articleSchema 模式推断出的类型; // 定义文章类型为由z库根据articleSchema模式推断出来的类型
Zod的实用用法Zod 允许定义复杂的验证规则,比如检查字符串长度,如:
z.string().min(6)
这些规则在TypeScript中没有直接对应的实现,在这种情况下,转换为string
类型。
那么你在 TypeScript 项目中会使用 Zod 哪些地方呢?
解析 API 响应数据“别相信后端”
API响应通常是未知或不可预测的数据的主要来源。你可以手动验证从fetch请求中获取的数据。
fetch(getArticle)
.then((response) => response.json())
.then((data) => {
return articleSchema.parse(data);
})
.catch(console.error);
首先获取文章,然后解析响应的JSON数据,如果出错则记录错误。
你可以用像Zodios这样的库(它是基于axios的)来这么做。
表单验证由于你无法控制传入数据的结构。你可能希望使外部数据更符合你的项目约定和逻辑。Zod 提供了
[ _.transform()_](https://github.com/colinhacks/zod/tree/v3#transform)
方法来在验证过程中使外部数据更符合项目约定和逻辑。
另一个外部数据来源是用户的输入。Zod 提供了一些内置的字符串验证工具和方法。当然,你也可以通过 [.refine()](https://github.com/colinhacks/zod/tree/v3#refine)
方法自定义验证规则。
const myString = z.string().refine((val) => val.length <= 255, {
message: "字符串长度不能超过255个字符",
});
如果你使用了 React,你可以在 react hook form 中使用 Zod 模式定义进行表单验证。
在谈区分类型吗?我从版本 1 开始使用 Zod,该版本包含了一个 .check()
方法,允许将模式用作 类型检查,这可以用于类型区分的条件中。
由于这个特性,很诱人采用“全模式”方案,并且使用 Zod 不仅 进行验证 还 进行类型区分。但很快发现这种方法其实不太划算。
这种方法已经在库的下一个版本中被移除。这是好事,因为 Zod专注于它最初的目的:外部数据的验证。对于大多数情况来说,类型缩小表达式已经足够。
摘要:如果你真的觉得内置表达式和if/else语句语法不够灵活,你也可以考虑一下这个库ts-pattern
TypeScript 默认设置太宽松。为了确保代码更安全,必须使用像 Zod 这样的工具来验证外部数据(本质上是未知的类型)。Zod 在验证不可预测的数据方面最有效,例如表单输入或 API 响应的数据。然而,对于大多数其他场景,类型断言表达式应该已经足够。
要了解更多详情关于数据验证,请参阅本文的后续文章(《数据验证的真相》)[1]。
[1] https://medium.com/ekino-france/zod-the-truth-about-data-validation-7de964f581fd
共同学习,写下你的评论
评论加载中...
作者其他优质文章