澄清TypeScript中的 `satisfies` 操作符

本文介绍了TypeScript中的satisfies运算符,它帮助解决类型过于宽泛的问题,允许推断最窄类型,同时对比了冒号注解、as注解和自动类型推断的使用场景和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

图片

TypeScript 的 satisfies 运算符已经推出一段时间了,但它似乎仍然是一个可以用来澄清的混乱来源。

可以把 satisfies 看作是将类型赋给值的另一种方式。

在我们深入研究之前,让我们回顾一下如何赋值类型。

首先,有一个不起眼的“冒号注解”(我们使用这个听起来有点像医学术语的名字,因为这个概念在 TS 文档中并没有真正给出一个名字)。

:表示“这个变量总是这个类型”:

 
const obj: Record<string, string> = {};obj.id = "123";

当你使用冒号注解时,你声明了变量是该类型。

这意味着你赋值给变量的东西必须是该类型:

 
// Type 'number' is not assignable to type 'string'.const str: string = 123;

给变量赋予的类型可能比你最初赋予的类型更宽。

 
let id: string | number = "123";if (typeof numericId !== "undefined") {  id = numericId;}

当你想要有一个默认值,而这个默认值以后可能会被重新赋值时,这个方法非常有用。

但是冒号注释有一个缺点。

当使用冒号时,类型优先于值。

换句话说,如果你声明的类型比你想要的宽,你就被这个宽的类型困住了。

例如,在下面的代码片段中,你没有在routes对象上获得 autocomplete:

 
const routes: Record<string, {}> = {  "/": {},  "/users": {},  "/admin/users": {},};// No error!routes.awdkjanwdkjn;

这就是  satisfies 被设计用来解决的问题。

这意味着它能推断出最窄的可能类型,而不是你指定的更宽的类型:

 
const routes = {  "/": {},  "/users": {},  "/admin/users": {},} satisfies Record<string, {}>;// Property 'awdkjanwdkjn' does not exist on type// '{ "/": {}; "/users": {}; "/admin/users": {}; }'routes.awdkjanwdkjn;

satisfies  还能防止在配置对象中指定错误的内容。

因此,satisfies  和 冒号注解 同样安全。

 
const routes = {  // Type 'null' is not assignable to type '{}'  "/": null,} satisfies Record<string, {}>;

另一种为变量赋值的方式是使用 “ as ” 注解。

与  satisfies  和冒号注解不同,使用 “ as ” 注解可以让你对 TypeScript 说谎。

在这个例子中,你在IDE中看不到错误,但它会在运行时崩溃:

 
type User = {  id: string;  name: {    first: string;    last: string;  };};const user = {} as User;// No error! But this will break at runtimeuser.name.first;

这些谎言有一些限制——你可以给对象添加属性,但是你不能在基本类型之间转换。

例如,你不能强制 TypeScript 将字符串转换为数字......

除非你用那个怪异的“as-as”:

 
// Conversion of type 'string' to type 'number'// may be a mistake because neither type// sufficiently overlaps with the other.const str = "my-string" as number;const str2 = "my-string" as unknown as number;

下面是  as  的合法用法,它用于将对象转换为尚未构造的已知类型:

 
type User = {  id: string;  name: string;};// The user hasn't been constructed yet, so we need// to use 'as' hereconst userToBeBuilt = {} as User;(["name", "id"] as const).forEach((key) => {  // Assigning to a dynamic key!  userToBeBuilt[key] = "default";});

警告:如果你使用  as  作为注解变量的默认方式,那几乎肯定是错误的!

下面的代码看起来很安全,但是一旦你给 User 类型添加了另一个属性, defaultUser 就过时了,而且它不会显示错误!

 
type User = {  id: string;  name: string;};const defaultUser = {  id: "123",  name: "Matt",} as User;

还有一种方法可以给变量赋类型:

什么都不加。

这不是打字错误!

TypeScript 在推断变量类型方面做得很好。

事实上,大多数情况下,你根本不需要输入变量:

 
const routes = {  "/": {},  "/users": {},  "/admin/users": {},};// OK!routes["/"];// Property 'awdahwdbjhbawd' does not exist on type// { "/": {}; "/users": {}; "/admin/users": {}; }routes["awdahwdbjhbawd"];

总结一下,我们有四种方法来给变量赋类型:

  • 冒号注解

  • satisfies

  • as 注解

  • 不注解,让TS推断


由于有不同的方法来做类似的事情,它可能会让人们对何时使用何种方法感到有点困惑。

简单的用例是  satisfies  最适用的:

 
type User = {  id: string;  name: string;};const defaultUser = {  id: "123",  name: "Matt",} satisfies User;

但是大多数时候,当你想给变量赋一个类型时,你可能希望这个类型更宽。

如果这个例子使用的是 satisfies ,那么就不能把 numericId 赋给 id :

 
// colon annotationlet id: string | number = "123";if (typeof numericId !== "undefined") {  id = numericId;}// satisfieslet id = "123" satisfies string | number;if (typeof numericId !== "undefined") {  // Type 'number' is not assignable to type 'string'.  id = numericId;}

经验法则是,你应该只在以下两种特定情况下使用satisfies:

  • 你想要的是变量的精确类型,而不是宽泛类型。

  • 这个类型足够复杂,你需要确保你没有把它搞砸。

欢迎关注公众号:文本魔术,了解更多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值