3 个必须掌握的 TypeScript 高阶概念

TypeScript 不仅仅是在 JavaScript 中添加类型注解那么简单------它是一个超集,能够帮助你编写更安全、更可预测、更易维护的代码。当你熟悉了基础语法后,往往会遇到一些需要"更强火力"的场景。本文将介绍三个强大的 TypeScript 概念(假设你已具备 TS 基础知识):

  • 递归类型
  • 映射类型与条件类型
  • 支持类型推断的工具类型

1. 递归类型

你没看错,TypeScript 中同样存在递归。递归类型是一种用于描述可以包含自身的数据结构的方式,例如树、链表或深度嵌套的对象。

一个经典的例子是用于表示 JSON 数据的类型,它可以是字符串、数字、布尔值、JSON 值数组,或包含更多 JSON 值的对象。

typescript 复制代码
type JSONValue =
  | string
  | number
  | boolean
  | null
  | JSONValue[]
  | { [key: string]: JSONValue };

// 示例用法:
const data: JSONValue = {
  name: "Alice",
  age: 30,
  friends: [
    { name: "Bob", age: 31 },
    { name: "Carol", age: 28 }
  ],
  address: null
};

递归类型让 TypeScript 能够描述并约束复杂嵌套数据结构的形态,使其既强大又易于阅读和理解。

再看一个直观的例子:

typescript 复制代码
type TreeNode<T> = {
  value: T;
  children?: TreeNode<T>[];
};

// 示例用法:
const tree: TreeNode<string> = {
  value: "root",
  children: [
    {
      value: "child1",
      children: [
        { value: "grandchild1" }
      ]
    },
    {
      value: "child2"
    }
  ]
};

2. 映射类型与条件类型

2.1 映射类型

映射类型允许你通过转换现有类型的属性来创建新类型。你可以将所有属性变为可选、只读,或修改它们的类型。

typescript 复制代码
type User = {
  id: number;
  name: string;
  isActive: boolean;
};

// 将所有属性变为可选
type OptionalUser = {
  [K in keyof User]?: User[K];
};

// 将所有属性变为只读
type ReadonlyUser = {
  readonly [K in keyof User]: User[K];
};

TypeScript 内置了常用的映射类型,如 Partial<T>Readonly<T>Record<K, T>,我们稍后会进一步了解。

2.2 条件类型

就像递归一样,条件判断在 TypeScript 类型系统中同样存在。条件类型让你可以根据其他类型来选择当前类型,类似于三元表达式,但作用于类型层面。

typescript 复制代码
interface Person {...}
interface User extends Person {...}
interface Bot {...}

type Visitor<T> = T extends Person ? User : Bot;

你可以使用条件类型构建灵活的 API、过滤类型或从类型中提取信息,从而实现高级的类型计算。

过滤类型:

假设你想排除一个类型中所有字符串属性。下面的 ExcludeStrings 使用条件类型和 as 映射,过滤掉属性类型为字符串的键:

typescript 复制代码
type ExcludeStrings<T> = {
  [K in keyof T as T[K] extends string ? never : K]: T[K]
};

type User = {
  id: number;
  name: string;
  isActive: boolean;
};

type NonStringProps = ExcludeStrings<User>; 
// 结果: { id: number; isActive: boolean }

从类型中提取信息:

获取函数的返回类型:

typescript 复制代码
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type MyFunc = (x: number) => Promise<string>;
type Result = ReturnType<MyFunc>; // 结果: Promise<string>

构建灵活 API:

创建一个能根据输入类型自适应输出类型的类型:

typescript 复制代码
type Flatten<T> = T extends Array<infer U> ? U : T;

type A = Flatten<number[]>;      // 结果: number
type B = Flatten<string>;        // 结果: string
type C = Flatten<boolean[][]>;   // 结果: boolean[]

3. 工具类型

工具类型是 TypeScript 内置的用于操作类型的辅助类型:

  • Record<K, T>:构建键为 K、值为 T 的对象类型
  • Omit<T, K>:从类型 T 中移除指定的键 K
  • ReturnType<T>:获取函数的返回类型
  • Parameters<T>:获取函数的参数类型
typescript 复制代码
interface User {
  id: string;
  name: string;
  email: string;
  friends?: string[];
  meta: {...};
}

type DisplayedUser = Omit<User, 'meta'>;

// 也可以通过联合类型一次移除多个属性
type LeanUser = Omit<User, 'friends' | 'meta'>;

type UserRecord = Record<number, User>;

const userRecord: UserRecord = {
  1: { id: '1', name: 'Alice', email: 'alice@example.com', friends: [...], meta: {} },
  2: { id: '2', name: 'Bob', email: 'bob@example.com', meta: {} },
};

类型推断与工具类型让你可以基于已有类型编写通用、可复用的代码,而无需额外定义新类型。

就我个人而言,PartialRecordOmit 非常实用且高频出现,但工具类型远不止这些,每种都有其独特的适用场景。你可以在 TypeScript 官方文档中探索所有工具类型。


总结

以上概念可能不会每天用到,但在合适的场景中运用它们,能帮你省去大量纠结的时间,避免编写过度复杂或冗余的类型。

掌握这些高阶概念将帮助你更精确、轻松地建模数据,编写更清晰安全的代码,并构建出灵活的 API。

如果你觉得这些内容有帮助,或者你有使用这些模式(或其他模式/概念)的成功案例,欢迎在评论区分享!

相关推荐
JarvanMo18 小时前
Flutter:使用图像作为屏幕背景
前端
Mintopia18 小时前
💰 金融Web应用中的AIGC风险控制技术与合规适配
前端·javascript·aigc
Mintopia18 小时前
🚀 Next.js 压力测试与性能调优实战
前端·javascript·全栈
江城开朗的豌豆18 小时前
TypeScript 类型系统漫游指南:从入门到爱上类型安全
前端·javascript
江城开朗的豌豆18 小时前
从 JavaScript 到 TypeScript:我为什么选择了类型守护
前端·javascript
杨超越luckly19 小时前
HTML应用指南:利用POST请求获取全国爱回收门店位置信息
大数据·前端·python·信息可视化·html
鹏多多19 小时前
解锁flutter弹窗新姿势:dialog-flutter_smart_dialog插件解读+案例
前端·flutter·客户端
IT_陈寒19 小时前
Redis 7.0的这个新特性让我处理百万级QPS轻松了50%,你可能还不知道!
前端·人工智能·后端
2301_7965125219 小时前
Rust编程学习 - 如何快速构建一个单线程 web server
前端·学习·rust
蒜香拿铁19 小时前
Angular【核心特性】
前端·javascript·angular.js