令人兴奋🤯 TypeScript 技巧一个小注释...内置类型🥱有趣的东西😋

2025-05-26

令人震撼的🤯 TypeScript 技巧

一点小提示...

内置类型🥱

多汁的东西😋

在我的博客上以浅色深色棕褐色模式阅读

抱歉标题太坑了🙃。不过这完全是出于好意,因为我要介绍一些 TypeScript 相关的技巧,它们一定会让你大吃一惊。如果你能读完整篇文章,对任何一个技巧都不感到吃惊,那就太好了!你已经是 TypeScript 高手了🥳

那么让我们直奔主题吧。

一点小提示...

本文的级别为高级。您可能不了解它们的工作原理。但是,您不必这样做。您只需复制粘贴代码片段,并了解如何使用它,因为它们会让您的工作变得轻松,并且随着时间的推移,您将掌握它们的实际工作原理。

内置类型🥱

这些是 TypeScript 中的一些内置辅助类型。我会尽量简短地介绍这些内容,因为你可以在任何地方阅读。TypeScript文档是一个不错的起点,之后我们再深入探讨一些精彩内容 😋

挑选

它允许从类型/接口中选择特定字段及其类型,并创建一个全新的类型。我们来看看这个 👇

type UserFields = {
  id: number;
  name: string;
  gender: 'male' | 'female' | 'non-binary' | 'prefer-not-to-say';
  dob: Date;
};

type NameAndGenderOnly = Pick<UserFields, 'name' | 'gender'>;

// This is equal to 👇
type NameAndGenderOnly = {
  name: string;
  gender: 'male' | 'female' | 'non-binary' | 'prefer-not-to-say';
};
Enter fullscreen mode Exit fullscreen mode

看到了吗?!相同的类型,没有任何重复。

部分的

这是我最常用的类型。如果你有一个类型/接口,并且出于某种原因,你想让它的所有字段都可选,那就用这个吧👇

type UserFields = {
  id: number;
  name: string;
  gender: 'male' | 'female' | 'non-binary' | 'prefer-not-to-say';
  dob: Date;
};

type OptionalUserFields = Partial<UserFields>;

// This is equal to 👇
type OptionalUserFields = {
  id?: number;
  name?: string;
  gender?: 'male' | 'female' | 'non-binary' | 'prefer-not-to-say';
  dob?: Date;
};
Enter fullscreen mode Exit fullscreen mode

只读

当你想确保对象的属性在代码中无法更改时,这非常有用。可以将其视为const对象属性的。

type UserFields = {
  id: number;
  name: string;
  gender: 'male' | 'female' | 'non-binary' | 'prefer-not-to-say';
  dob: Date;
};

const userData: Readonly<UserFields> = {
  id: 100,
  name: 'Puru Vijay',
  gender: 'male',
  dob: new Date('12 Nov, 2001'),
};
Enter fullscreen mode Exit fullscreen mode

尝试修改任何属性userData.name = 'Hoolalala'都会导致错误。

记录

Record现在我们开始聊聊好东西。最近,在开发我当前的项目macos.now.shShameless Plug,它基本上是用 Preact 和 Vite 编写的 macOS Big Sur 克隆版)时,我对它产生了新的敬意。

看看这个👇

export type AppName =
  | 'finder'
  | 'launchpad'
  | 'safari'
  | 'messages'
  | 'mail'
  | 'maps'
  | 'photos'
  | 'facetime'
  | 'calendar';

/** Which apps are currently open */
export const openApps: Record<AppName, boolean> = {
  finder: false,
  launchpad: false,
  safari: false,
  messages: false,
  mail: false,
  maps: false,
  photos: false,
  facetime: false,
  calendar: false,
};
Enter fullscreen mode Exit fullscreen mode

如您所见,这只是一个简单的键值对。但我希望强制此对象包含AppName联合类型中列出的所有应用,并且所有值均为布尔值。此外,我还希望在向列表中添加新应用时出现错误提示,这将导致我将该应用的键值对添加到此openApps对象中。

这就是RecordTypeScript 的用武之地。它只是一种强制键和值类型的方法。这是 TypeScript 添加的另一层安全保障。

多汁的东西😋

现在有趣的部分开始了。

从数组中检索元素类型

假设你有一个数组,你想从数组中提取每个元素的类型

type ArrayElement<
  ArrayType extends readonly unknown[]
> = ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
Enter fullscreen mode Exit fullscreen mode

我们infer在这里使用 TypeScript,它有助于从复杂类型中挑选出特定类型。

使用方法如下:

type A = ArrayElement<string[]>; // string
type B = ArrayElement<readonly string[]>; // string
type C = ArrayElement<[string, number]>; // string | number
type D = ArrayElement<['foo', 'bar']>; // "foo" | "bar"
type E = ArrayElement<(P | Q | R)[]>; // P | Q | R

type Error1 = ArrayElement<{ name: string }>;
//                         ^^^^^^^^^^^^^^^^
// Error: Type '{ name: string; }' does not satisfy the constraint 'readonly unknown[]'.
Enter fullscreen mode Exit fullscreen mode

有一个稍微简单的版本来获取元素类型。

type ArrayElement<ArrayType extends readonly unknown[]> = ArrayType[number];
Enter fullscreen mode Exit fullscreen mode

从承诺中检索类型

有没有想过从返回 Promise 的函数中获取类型?你可能尝试过这样做:

function returnsPromise(): Promise<number>;

let num: typeof returnsPromise;
//       ^^^^^^^^^^^^^^^^^^^^^
// num: () => Promise<number>
Enter fullscreen mode Exit fullscreen mode

我们希望num的类型成为承诺的返回类型(在本例中为number),而上述解决方案肯定不起作用。

解决方案是再次使用infer从承诺中检索类型:

type UnwrapPromise<T> = T extends (props: any) => PromiseLike<infer U>
  ? U
  : T extends PromiseLike<infer K>
  ? K
  : T;
Enter fullscreen mode Exit fullscreen mode

用法:

function returnsPromise(props: any) {
  return Promise.resolve(6);
}

const num: UnwrapPromise<typeof returnsPromise> = 8;
//    num: number
Enter fullscreen mode Exit fullscreen mode

这里我们将一个返回 Promise 的函数包装到了这个类型中。这Promise<unknown>也可以直接用于常规类型。

为什么PromiseLike而不是Promise \

Promise接口自带了许多 Promise 独有的预建方法。但有时,你想创建一个.then像 Promise 一样返回 a 的函数,但又不具备 Promise 的所有属性Promise。在这种情况下,我们使用PromiseLike

补充一下:你可以重命名UnwrapPromiseBreakPromise。这不会影响代码,但能让人开心一下 🤣🤣

将元组转换为联合类型

这是一个元组:

const alphabets = ['a', 'b', 'c', 'd'] as const;
Enter fullscreen mode Exit fullscreen mode

注意:如果没有as const结尾,typescript 会将类型解释为string[],而不是元组

现在我们想将这些特定的字符串用作联合类型。很简单。

type Alphabet = 'a' | 'b' | 'c' | 'd';
Enter fullscreen mode Exit fullscreen mode

这样就行了。但是,假设这个类型和上面的数组最终会放在不同的文件中,而且项目变得很大,几个月后你回来,给变量添加了另一个值ealphabets然后 BOOM!!! 整个代码库都崩溃了,因为你忘了添加e联合Alphabet类型。

我们可以自动化Alphabet联合类型的生成,以便它直接从alphabets变量中提取其成员。

type Alphabet = typeof alphabets[number];
Enter fullscreen mode Exit fullscreen mode

这是通用类型安全助手:

type UnionFromTuple<Tuple extends readonly (string | number | boolean)[]> = Tuple[number];
Enter fullscreen mode Exit fullscreen mode

用法:

const alphabets = ['a', 'b', 'c', 'd'] as const;

type Alphabet = UnionFromTuple<typeof alphabets>;
//  type Alphabet = 'a' | 'b' | 'c' | 'd'
Enter fullscreen mode Exit fullscreen mode

为什么readonly array \
\
本节讲的是元组到联合类型的转换,但在代码中我们并没有使用这个词tuple。原因是 tuple 不是关键字。就 TypeScript 而言,areadonly Array就是一个元组。它没有Tuple类型或其他信息。这就是为什么我要确保传递给它的类型UnionFromTuple是元组,而不是数组。如果是数组,它基本上与上面我们从数组中检索元素类型的部分相同。

来自对象的联合类型

假设我们有这个对象:

const openApps = {
  finder: false,
  launchpad: false,
  safari: false,
  messages: false,
  mail: false,
  maps: false,
  photos: false,
  facetime: false,
  calendar: false,
};
Enter fullscreen mode Exit fullscreen mode

我想创建一个基于此处指定的键的联合类型。如果我向此对象添加一个额外的键值对,我希望联合类型也包含该键值对。

解决方案如下:

type KeysOfObject<T extends { [K in string | number]: unknown }> = keyof T;
Enter fullscreen mode Exit fullscreen mode

用法👇

type App = KeysOfObject<typeof openApps>;
Enter fullscreen mode Exit fullscreen mode

这将等于👇

type App =
  | 'finder'
  | 'launchpad'
  | 'safari'
  | 'messages'
  | 'mail'
  | 'maps'
  | 'photos'
  | 'facetime'
  | 'calendar';
Enter fullscreen mode Exit fullscreen mode

更好的 Object.Keys

这篇文章看起来就像是各种辅助类型的汇总,事实也确实如此。但在这篇文章中,我要分享一个技巧,它既不是最令人兴奋,也不是最酷炫的。它很无聊,但重要的是,它是整篇文章中最有用的技巧。如果你需要从这篇文章中学到什么,那就从这个技巧开始吧。除了这部分,其他的都可以忽略。

让我们看一下之前的对象:

const openApps = {
  finder: false,
  launchpad: false,
  safari: false,
  messages: false,
  mail: false,
  maps: false,
  photos: false,
  facetime: false,
  calendar: false,
};
Enter fullscreen mode Exit fullscreen mode

假设我想申请Object.keys获取该对象的键数组。

const apps = Object.keys(openApps);
//  ["finder", "launchpad", "safari", "messages", "mail", "maps", "photos", "facetime", "calendar"]
Enter fullscreen mode Exit fullscreen mode

但这里有个小问题。如果你把鼠标悬停在 上apps,它的类型会是string[],而不是("finder" | "launchpad" | "safari" | "messages" | "mail" | "maps" | "photos" | "facetime" | "calendar")[]

从本质上来说,这并不是一个问题Object.keys,但如果能返回键的联合类型数组就太好了。

Object.keys那么让我们来调查一下这个问题。我们从预建的定义开始lib.d.ts

interface ObjectConstructor {
  //...
  keys(o: object): string[];
  keys(o: {}): string[];
}
Enter fullscreen mode Exit fullscreen mode

如果你觉得它keys被定义两次很奇怪,它被称为函数/方法重载。基本上,您可以定义多个函数声明以便灵活使用。

如你所见,它被硬编码为始终返回string[]。我相信它存在是有充分理由的,但对我来说相当不方便,所以我将重写此方法,以便根据传递的内容正确推断出键。

如果您.d.ts的项目中有一个根文件,请将下面的代码片段放入其中。

type ObjectKeys<Obj> = Obj extends object
  ? (keyof Obj)[]
  : Obj extends number
  ? []
  : Obj extends Array<any> | string
  ? string[]
  : never;

interface ObjectConstructor {
  keys<ObjectType>(o: ObjectType): ObjectKeys<ObjectType>;
}
Enter fullscreen mode Exit fullscreen mode

现在让我们用新的尝试上面的代码Object.keys

const apps = Object.keys(openApps);
// const apps: ("finder" | "launchpad" | "safari" | "messages" | "mail" | "maps" | "photos" | "facetime" | "calendar")[]
Enter fullscreen mode Exit fullscreen mode

不相信我?自己去TypeScript Playground看看吧。

注:所有功劳都归功于 Steven Baumgeitner 的博客文章,他讲的也是同样的道理。我只是抄袭了它😁。你可以Object.keys在他的博客文章中阅读更多关于修复的内容。

好了,就是这样了!!希望你从这篇博文中有所收获!

签字结束!!

文章来源:https://dev.to/puruvj/mindblowing-typescript-tricks-46nf
PREV
我用于前端开发的工具
NEXT
面向开发人员的 Linux 命令