高级 TypeScript 技巧:Conditional Types、Mapped Types 和 Type Inference 策略,提升代码质量

发布: (2026年1月6日 GMT+8 20:08)
10 min read
原文: Dev.to

Source: Dev.to

作为畅销书作者,我邀请您在 Amazon 上探索我的书籍。别忘了在 Medium 上关注我并表达您的支持。谢谢!您的支持意义重大!

Source:

TypeScript 的类型系统 – 超能力

TypeScript 的类型系统常常像一种可以逐步学习的超能力。我最初只使用简单的注解——stringnumber、基本接口——随着时间的推移,我发现了另一层技术,它们让我能够以惊人的精确度描述代码的形状和行为。这些方法在代码运行前就捕获错误,并让编辑器显得格外聪明。下面是一些最强大方法的逐步演示,从基础到高级。

1. 条件类型

把它们想象成类型的 “if 语句”。它们可以让一个类型根据另一个类型来决定自身的形态,这在创建灵活的工具函数时非常有用。

type IsNumber<T> = T extends number ? true : false;

type TestA = IsNumber<string>; // false
type TestB = IsNumber<42>;    // true

条件类型中的 infer

捕获另一个类型的一部分。经典例子是提取你还不知道的函数的返回类型。

type WhatDoesThisReturn<T> =
  T extends (...args: any) => infer ReturnType ? ReturnType : never;

function fetchData() {
  return { id: 1, title: 'Item' };
}

type FetchedData = WhatDoesThisReturn<typeof fetchData>;
// FetchedData is now { id: number; title: string; }

对联合类型的分配

条件类型会自动对联合成员进行分配。

type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]

阻止分配

将检查包装在元组中即可停止分配。

type ToArrayNoDistribute<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNoDistribute<string | number>; // (string | number)[]

2. 映射类型

通过转换已有类型的每个属性来创建新类型——类似于 Array.map(),但作用于对象键。

基础映射类型

type MakeAllOptional<T> = {
  [Key in keyof T]?: T[Key];
};

type MakeAllReadonly<T> = {
  readonly [Key in keyof T]: T[Key];
};

interface User {
  id: number;
  name: string;
}

type PartialUser = MakeAllOptional<User>;
// { id?: number; name?: string; }

type ReadonlyUser = MakeAllReadonly<User>;
// { readonly id: number; readonly name: string; }

添加 / 删除修饰符

使用 +- 来切换 ?(可选)或 readonly

type MakeRequired<T> = {
  [Key in keyof T]-?: T[Key];
};

type UserWithOptionalId = { id?: number; name: string };
type RequiredUser = MakeRequired<UserWithOptionalId>;
// { id: number; name: string; }

使用 as 进行键重映射

// 只保留函数属性
type FunctionProperties<T> = {
  [Key in keyof T as T[Key] extends Function ? Key : never]: T[Key];
};

// 为每个键添加 'get' 前缀
type Getters<T> = {
  [Key in keyof T as `get${Capitalize<string & Key>}`]: () => T[Key];
};

interface DataModel {
  value: number;
  calculate(): number;
}

type JustFunctions = FunctionProperties<DataModel>; // { calculate: () => number; }
type DataGetters = Getters<DataModel>;
// { getValue: () => number; getCalculate: () => () => number; }

3. 模板字面量类型

使用模板字符串语法操作字符串字面量类型。非常适合类型安全的路由、CSS 类名或方法名。

type Color = 'red' | 'blue';
type Size = 'sm' | 'lg';

type ButtonClass = `btn-${Size}-${Color}`;
// "btn-sm-red" | "btn-sm-blue" | "btn-lg-red" | "btn-lg-blue"

const myClass: ButtonClass = 'btn-lg-red'; // ✅ works
// const badClass: ButtonClass = 'btn-md-green'; // ❌ error

使用 infer 解析字符串

type ExtractParts<T> = T extends `btn-${infer S}-${infer C}` ? [S, C] : never;
type Parts = ExtractParts<'btn-sm-red'>; // ["sm", "red"]

4. 推断运算符

typeof – 获取值的静态类型

const config = { host: 'localhost', port: 3000 };
type Config = typeof config;
// { host: string; port: number; }

keyof – 对象属性名的联合类型

(后续内容请参见下一部分)

Source:

属性键

interface Product {
  id: number;
  name: string;
  price: number;
}
type ProductKey = keyof Product; // "id" | "name" | "price"

索引访问类型 – 查找属性的类型

type ProductNameType = Product['name'];   // string
type ProductPriceType = Product['price']; // number

这些技术——条件类型、映射类型、模板字面量类型以及推断操作符——构成了一个强大的工具箱,让你能够编写更安全、更具表现力的 TypeScript。尝试使用它们,进行组合,你会发现编辑器变成了真正的编码伙伴。祝你编码愉快!

索引访问类型、实用工具类型和泛型在 TypeScript 中的使用

下面提供简洁的示例,演示如何使用 索引访问类型实用工具类型泛型约束递归类型

索引访问类型

interface Product {
  id: number;
  name: string;
  price: number;
}

// 获取单个属性的类型
type ProductId = Product['id']; // number

// 多个属性的联合类型
type IdOrName = Product['id' | 'name']; // number | string

深层索引访问

interface APIResponse {
  data: {
    user: {
      id: string;
      profile: { name: string };
    };
  };
}

// 提取深层嵌套属性的类型
type UserProfileName = APIResponse['data']['user']['profile']['name']; // string

实用工具类型:PickOmit

interface FullUser {
  id: number;
  name: string;
  email: string;
  passwordHash: string;
  createdAt: Date;
}

/* 面向公众的数据 */
type PublicUser = Pick<FullUser, 'id' | 'name' | 'email'>;
/* { id: number; name: string; email: string; } */

/* 移除敏感字段 */
type SafeUser = Omit<FullUser, 'passwordHash'>;
/* { id: number; name: string; email: string; createdAt: Date; } */

Pick 的工作原理(简化版)

// 对 Pick 的最小实现
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

type PickedName = MyPick<FullUser, 'name' | 'email'>;
// { name: string; email: string; }

对泛型类型参数进行约束

// 只接受具有 `id` 属性的对象的函数
function getById<T extends { id: string }>(items: T[], id: string): T | undefined {
  return items.find(item => item.id === id);
}

const users = [{ id: 'a1', name: 'Alice' }, { id: 'b2', name: 'Bob' }];
const found = getById(users, 'b2'); // { id: string; name: string; }

用一个泛型约束另一个泛型

// 合并两个对象;返回类型是两个输入的交叉类型
function merge<A, B>(a: A, b: B): A & B {
  return { ...a, ...b };
}

const result = merge({ foo: 1 }, { bar: 'two' });
// result: { foo: number; } & { bar: string; }

递归类型

树结构

interface TreeNode<T> {
  value: T;
  children?: TreeNode<T>[];
}

const tree: TreeNode<string> = {
  value: 'root',
  children: [
    { value: 'child1' },
    { value: 'child2', children: [{ value: 'grandchild' }] }
  ]
};

深度可选 (DeepPartial)

type DeepPartial<T> = T extends object
  ? { [P in keyof T]?: DeepPartial<T[P]> }
  : T;

interface Settings {
  user: {
    theme: string;
    dashboard: { widgets: string[] };
  };
}

type PartialSettings = DeepPartial<Settings>;
/* 所有属性(user、theme、dashboard、widgets)在任意深度下都变为可选 */

小结

使用 typeofkeyof 可以避免重复,然后通过 映射类型条件类型 构建小型实用工具类型,以解决特定问题(例如,从接口中提取所有字符串属性)。随着时间的推移,这些技巧会成为你的日常工作流的一部分,使 TypeScript 编译器成为真正的伙伴,强制执行契约并减少错误。

📘 查看我的最新电子书

在 YouTube 上观看免费预览

如果你喜欢这些内容,请 点赞分享评论订阅

101 Books

101 Books 是一家由作者 Aarav Joshi 共同创立的 AI 驱动出版公司。通过利用先进的 AI 技术,我们将出版成本保持在低位——部分书籍的定价低至 $4——让优质知识触手可及。

  • Golang Clean Code – 在 Amazon 上可购
  • 搜索 Aarav Joshi 以发现更多书籍并享受 特别折扣

我们的创作

探索我们的项目:

我们在 Medium

[Tech Koala 洞察]https://medium.com/@techkoala)—— 关注以获取更多文章、教程以及对现代开发主题的深入探讨。

[Tech Koala 洞察]https://techkoalainsights.com/) | [时代与回声世界]https://world.epochsandechoes.com/) | [Investor Central Medium]https://medium.investorcentral.co.uk/) | [谜团之谜 Medium]https://medium.com/puzzling-mysteries) | [科学与时代 Medium]https://science.epochsandechoes.com/) | [现代 Hindutva]https://modernhindutva.substack.com/)

Back to Blog

相关文章

阅读更多 »

2026年我想学习的3件事

n8n 这个工具已经被一些开发者 YouTuber 多次介绍,激起了我的兴趣。它是一个 open-source workflow automation tool,采用 fair‑code licensed,功能强大……

简明 TypeScript 书

请提供您希望翻译的具体摘录或摘要内容,我才能为您进行简体中文翻译。