Contents

TypeScript 使用技巧(一)

1. 枚举类型

1.1 获取枚举(enum)的键(key)

enum MyEnum {
  Key1 = 'Value1',
  Key2 = 'Value2',
  Key3 = 'Value3',
}

type MyEnumKeys = keyof typeof MyEnum;

// MyEnumKeys 的类型为 "Key1" | "Key2" | "Key3"

1.2 获取枚举(enum)的值(value)

enum MyEnum {
  Key1 = 'Value1',
  Key2 = 'Value2',
  Key3 = 'Value3',
}

type MyEnumValues = MyEnum[keyof typeof MyEnum];

// MyEnumValues 的类型为 "Value1" | "Value2" | "Value3"

2.新特性 satisfies

satisfies 运算符提供了一种为值添加类型注解而不丢失值推理的方法。

2.1 使用satisfies的强类型 URLSearchParams

satisfies非常适合强类型化函数,这些函数通常使用更宽松的类型。

在使用 URLSearchParams 时,它的参数通常是 Record<string, string | string[]> 。这是一种非常松散的类型,并不强制执行任何特性的键。通常情况下,你需要创建一些搜索参数并将它们传递给 URL。因此,这种松散的类型最终会变得相当危险。

这时,satisfies 就起作用了。

type URLParams = {
  title: string;
  body: string;
};

const params = new URLSearchParams({
  title: 'title',
} satisfies URLParams);
// ❌ 类型 "{ title: string; }" 中缺少属性 "body",但类型 "URLParams" 中需要该属性。

2.2 带 satisfies 的强类型 POST 请求

在发出 POST 请求时,向服务器发送正确的数据结构非常重要。服务器会期望请求正文有特定的格式,但使用 JSON.stringify 将其转化为 JSON 的过程会消除任何强类型。

type Post = {
  title: string;
  content: string;
};
fetch('/api/posts', {
  method: 'POST',
  body: JSON.stringify({
    title: 'New Post',
    content: 'Lorem ipsum.',
  } satisfies Post),
});

我们可以用 Post 类型注释请求正文,确保标题和内容属性存在且类型正确。

2.3 使用 satisfies 而不是 as const 来推断元组

通常,你会希望在 TypeScript 中声明一个元素数组,但将其推断为元组而非数组。你可能会使用 as const 断言来推断元组类型,而不是数组类型。然后,使用 satisfies 操作符,你可以在不使用 as const 的情况下获得相同的结果。

type MoreThanOneMember = [any, ...any[]];
const array = [1, 2, 3];
const maybeExists = array[3];
const tuple = [1, 2, 3] satisfies MoreThanOneMember;
// ❌ 长度为 "3" 的元组类型 "[number, number, number]" 在索引 "3" 处没有元素。
const doesNotExist = tuple[3];

在上面的代码中,我们以两种不同的方式声明数组。如果我们不使用 satisfies对其进行注解,它就会被推断为 number[]类型。这意味着当我们尝试访问数组中不存在的元素时,TypeScript 不会给出错误信息;他只是将其推断为 number | undefined

然而,当我们使用 satisfies 时,TypeScript 会正确推断出数组类型为 MoreThanOneMember,一个包含三个元素的元组,并给出错误信息。

2.4 使用 satisfies 强制 as const 对象成为某种类型。

使用 as const 时,我们可以指定将对象视为具有字面类型的不可变值。但是,这并不能强制对象具有任何特定的形状或属性。要强制 as const 对象具有特定的形状,我们可以使用 satisfies 操作符。

在下面的示例中,我们有一个 RouteObject 类型,它代表了一个路由集合。每个路由都有一个字符串类型的 url 属性和一个可选的 searchParams 属性。我们要确保路由对象满足 RouteObject 类型。

type RouteObject = Record<
  string,
  {
    url: string;
    searchParams: Record<string, string | string[]>;
  }
>;

const route = {
  home: {
    url: '/',
    searchParams: {},
  },
  // ❌ 类型 "{ readonly url: "/about"; }" 中缺少属性 "searchParams",但类型 "{ url: string; searchParams: Record<string, string | string[]>; }" 中需要该属性。
  about: {
    url: '/about',
  },
} as const satisfies RouteObject;

2.5 使用 satisfies 来强制 as const 数组成为特定类型。

type NavElement = {
  title: string;
  url?: string;
  children?: readonly NavElement[];
};
const nav = [
  {
    title: 'Home',
    url: '/',
  },
  {
    title: 'About',
    children: [
      {
        title: 'Team',
        url: '/about/team',
      },
    ],
  },
] as const satisfies readonly NavElement[];

nav[0].children;
// ❌ 类型“{ readonly title: "Home"; readonly url: "/"; }”上不存在属性“children”。