Featured image of post 掌握 TypeScript 可以让 Operator 满意Featured image of post 掌握 TypeScript 可以让 Operator 满意

掌握 TypeScript 可以让 Operator 满意

了解 TypeScript 中的 satisfies 运算符如何根据自定义类型验证对象结构而不丢失精确的文字类型推断。

介绍

TypeScript 4.9 中引入的 satisfies 运算符是一项强大的功能,旨在提高类型安全性,同时保持开发人员的灵活性。

但是,区分 satisfies、类型注释 (: Type) 和类型断言 (as Type) 之间的差异可能会令人困惑。本指南详细介绍了 satisfies 运算符的机制,并分享了生产代码库的实际用例。


1. 标准类型注解的局限性(:)

让我们回顾一下标准类型注释的局限性。

想象一下设计一个调色板主题配置。有些颜色被命名为字符串文字,而其他颜色则为数字 RGB 数组:

type Color = string | [number, number, number];

// Palette definition
type Palette = {
  primary: Color;
  secondary: Color;
  danger: Color;
};

// Declaring our palette using standard type annotations
const themePalette: Palette = {
  primary: "blue",
  secondary: [0, 128, 0], // Green (RGB)
  danger: "red"
};

一切编译都很好。但是,如果我们访问 themePalette.primary 并尝试运行像 toUpperCase() 这样的字符串方法,编译器会抱怨:

// Error: Property 'toUpperCase' does not exist on type 'Color'.
// Property 'toUpperCase' does not exist on type '[number, number, number]'.
themePalette.primary.toUpperCase();

为什么会出现这个错误?

通过使用 : Palette 注释,我们指示 TypeScript 扩展 themePalette 的属性以匹配 Palette 类型定义。编译器会丢弃 primary 被分配了字符串 "blue" 的特定知识。相反,它假设 primary 在任何时候都可以是字符串或数组,迫使您编写繁琐的类型保护。


2. 解决问题满足

satisfies 运算符解决了这种情况。 它指示 TypeScript 验证对象是否与特定类型结构匹配,但保留指定值的精确、最窄的推断类型

重构满足

const themePalette = {
  primary: "blue",
  secondary: [0, 128, 0],
  danger: "red"
} satisfies Palette; // Verify compatibility with Palette but keep the literal types

使用 satisfies 可以带来两个直接的好处:

好处 1:精确类型推断保留

// Compiles perfectly!
// TypeScript knows themePalette.primary is definitely a string
themePalette.primary.toUpperCase();

// Also compiles perfectly!
// The compiler knows secondary is a 3-element tuple of numbers
themePalette.secondary.map(v => v * 2);

好处 2:及早发现拼写错误

由于编译器根据目标类型验证对象结构,因此任何丢失的属性或不正确的类型分配都将立即触发编译警告。

const badPalette = {
  primary: "blue",
  secondary: [0, 128, 0],
  danger: 12345, // Error: number is not assignable to type Color
  warning: "orange" // Error: warning does not exist on type Palette
} satisfies Palette;

3. 实际用例:保护记录上的对象密钥

另一个强大的用例是使用 Record 类型管理键值映射。

例如,定义用户访问权限时:

type UserRole = "admin" | "editor" | "viewer";

// Map each role to a boolean access value
const rolePermissions = {
  admin: true,
  editor: true,
  viewer: false
} satisfies Record<UserRole, boolean>;

此处使用 satisfies 可确保:

  1. UserRole 中声明的每个角色都必须在对象中定义。如果忘记包含 viewer,编译器会抛出错误。
  2. rolePermissions 的键仍输入为确切的角色字符串 ("admin" | "editor" | "viewer"),而不是一般字符串,从而启用 IDE 自动完成功能。

结论:类型定义指南

编写 TypeScript 时,请使用以下经验规则来选择正确的键入语法:

  1. 类型注释 (: Type): 将此用于函数参数、返回签名,或者当您明确想要扩大类型声明以限制对子属性的访问时。
  2. 类型断言 (as Type): 仅当解析外部 API 输入时使用此选项,其中 TypeScript 无法解析类型,但您绝对确定结构(谨慎使用)。
  3. satisfies 运算符 (satisfies Type): 在声明本地配置、常量或状态记录时使用它来验证它们是否遵循定义的结构,而不丢弃所分配值的确切类型。

使用 satisfies 运算符可以帮助您编写更干净、类型安全的代码,同时减少样板转换。