TypeScript 别再滥用 as,改用 satisfies

原文:https://dev.to/ogeobubu/you-are-lying-to-your-compiler-use-satisfies-instead-of-as-55j2(作者 @ogeobubu)

你一直在欺骗编译器,是时候停下来了。

下面这句话可能会让一部分人不舒服:在 TypeScript 里每次写下 as,本质上都是在命令编译器闭嘴、无条件相信你。那一瞬间感觉很有掌控力,但这恰恰是把 bug 带上生产环境的典型方式。

as 关键字不做任何检查。它是命令,不是提问。你等于在对编译器说:“我比你懂,不需要你帮忙。”偶尔这话确实成立,但大多数时候并不。

TypeScript 4.9 带来了一个更好的选择:satisfies。理解它的作用之后,你会纳闷以前没有它是怎么过来的。

as 到底做了什么

先把机制说清楚。当你写:

const user = { name: "Alice" } as User;


你执行的是一次类型断言(type assertion),也就是用你自己的判断覆盖 TypeScript 的类型推断。编译器只会确认这个断言“看起来合理”,并不会去验证你的对象真的具备全部必需属性。

问题就在这里:as 压制的是报错,它抓不住错误,只会把错误藏起来。

看这个例子:

interface User {
  name: string;
  email: string;
}

const user = {
  name: "Alice",
  username: "alice123"
} as User;


TypeScript 不会报任何错。但你刚刚制造了一个自称是 User 的对象——它缺了 email 属性,还多出一个 username 字段。等后续代码去读 user.email 时,运行时拿到的就是 undefined。

这恰恰是 TypeScript 本该防住的那种静默失败。

satisfies 登场

satisfies 运算符做的事不太一样:它不去覆盖类型,而是去校验类型。

同样的例子换成 satisfies:

const user = {
  name: "Alice",
  email: "alice@example.com"
} satisfies User;


如果把 email 拼错成 emial,或者干脆漏掉,TypeScript 会立刻抛错。IDE 当场报警,编译器直接拒绝构建。

更妙的是,satisfies 不会拓宽你的类型。

写成 const user: User = {...} 这种类型注解时,TypeScript 会把 user.name 的类型视为 string。而用 satisfies,TypeScript 会保留字面量类型:你写的是 name: "Alice",那么 user.name 的类型就是精确的 "Alice",而不只是 string。

实际差异有多大

来看一个真实场景,它能直接说明两者的区别。

假设你在定义一套调色板:

type ColorConfig = Record<string, [number, number, number]>;

// 用类型注解,会丢失精度
const colors: ColorConfig = {
  red: [255, 0, 0],
  green: [0, 255, 0],
  blue: [0, 0, 255],
};

colors.red;   // 类型:[number, number, number]
colors.pink;  // 不报错。TypeScript 不知道 "pink" 根本不存在


换成 satisfies:

const colors = {
  red: [255, 0, 0],
  green: [0, 255, 0],
  blue: [0, 0, 255],
} satisfies ColorConfig;

colors.red;   // 类型:[255, 0, 0],精确
colors.pink;  // 报错:Property 'pink' does not exist


第二种写法能对真实的键名做自动补全,能抓住拼写错误,还能保留精确的字面量值。这才是 TypeScript 本该有的体验。

什么时候仍然该用 as

这并不是说 as 一无是处。确实存在一些你比编译器知道得更多的合理场景。

最经典的是 DOM 查询:

const element = document.querySelector("#app") as HTMLDivElement;


TypeScript 不可能知道页面上挂的是什么元素,它只能返回 Element | null。而你知道那是一个 div。这正是 as 的用武之地。

其他合理用法还包括类型守卫(type guard),以及某些为了绕开 any 的边角情况。但对于对象字面量、配置文件和类型校验——也就是大多数人日常花时间最多的地方——satisfies 才是更好的工具。

心智模型

这三者可以这样理解。

类型注解(: Type)的意思是“这个变量就是这个类型,我不关心具体值”。

as 的意思是“编译器,我比你懂,别查了”。

satisfies 的意思是“确保这个值符合这个形状,但保留我推断出的精确类型”。

第三种几乎总是你真正想要的那个。

别再撒谎,开始校验

TypeScript 社区已经在朝这个方向走,甚至出现了在 lint 规则层面建议优先使用 satisfies 而非 as 的提案,理由正是上面这些。

下次伸手要写 as 的时候,先问自己一个问题:在这里,我真的比编译器更聪明,还是只是在偷懒?

如果是后者,试试 satisfies。未来的你会感谢现在的自己。

原文:https://dev.to/ogeobubu/you-are-lying-to-your-compiler-use-satisfies-instead-of-as-55j2(作者 @ogeobubu)

发布评论
全部评论(0)