TypeScript 部分指南

11 阅读6分钟

interface & type

两者都能定义对象结构和函数签名,但interface 更偏向结构扩展与 OOP 语义;type 更偏向类型组合与类型表达式。

在工程中,interface 用于描述数据结构,type 用于类型运算和复杂组合。

相同点

  • 都能描述对象形状
  • 都能做函数类型定义
  • 都支持泛型
  • 都能被 class implements
interface A { x: number }
type B = { x: number }

核心区别

  1. interface 可以合并声明
interface User { id: number }
interface User { name: string }

const u: User = { id: 1, name: 'a' }

👉 type 不行,这是 interface 最重要的能力。

适合用来扩展第三方库定义(例如扩展 Express Request)。

  1. type 更灵活,支持类型表达式

type 可以:

  • 联合
  • 交叉
  • 条件类型
  • 映射类型
  • 模板字符串类型
type Status = 'success' | 'error';
type WithId<T> = T & { id: string };
type Api<T> = T extends any[] ? 'array' : 'object';

👉 interface 做不到这些。

  1. interface 更适合建模“对象、类、API”结构
interface Person {
  name: string;
  say(): void;
}

它能:

  • 扩展其他 interface(extends)
  • 被类 implements(非常 OOP)
  1. type 可以“别名化”(alias)任何类型
type Fn = (x: number) => void;
type Point = [number, number];
type Maybe<T> = T | undefined;

👉 type 的使用范围更广,甚至可以 alias 基本类型。
interface 做不到。

  1. 扩展方式不同(extends vs 交叉类型)
  • interface 扩展
interface A { x: number }
interface B extends A { y: number }
  • type 扩展
type A = { x: number }
type B = A & { y: number }

两者效果一致,但交叉类型能做更多复杂组合。

  1. 复杂类型构造:type 更强

前端工程里的工具类型、复杂泛型、联合类型转换 几乎都是 type 实现的

type Partial<T> = {
  [P in keyof T]?: T[P];
}

interface 无法实现这种类型运算。

工程中如何选择

如果是 描述数据结构、业务对象、class API,优先用 interface,因为其更可扩展、可合并,团队协作更友好。

如果是 类型组合、联合类型、工具类型、映射类型、模板字面量,一定用 type,因为更灵活。

unknow & any & never

🐱unknown

unknown 是安全的 any,可以接受任何值,但不能被直接使用,需要类型收窄。适用于以下的严格使用场景。

  1. 不信任来源的数据(API、用户输入、动态数据)
function parse(json: string): unknown {
  return JSON.parse(json);
}
  1. 泛型上界不确定,但不希望失去类型安全
function handle<T extends unknown>(value: T) {}
  1. 设计库时需要“类型安全的扩展点”

比如写 SDK、插件系统时,unknown 让调用方必须显式确认类型。

🐶never

never 代表“不可能发生的值”(类型系统的底部类型)

  • 程序不会走到这里
  • 函数不会返回
  • 分支被完全排除

适用于以下的严格使用场景。

  1. 表示函数永远不返回(异常 / 死循环)
function fail(msg: string): never {
  throw new Error(msg);
}
  1. 类型收窄后出现“不可能的情况”
function assertNever(x: never): never {
  throw new Error("Unexpected value: " + x);
}

配合基础类型保护写 exhaustiveness check:

type Shape = 'circle' | 'square';

function area(s: Shape) {
  switch (s) {
    case 'circle': return 1;
    case 'square': return 1;
    default: 
      return assertNever(s); // 编译期报错,保证分支覆盖完整
  }
}
  1. 联合类型排除成员
type Exclude<T, U> = T extends U ? never : T;

“never 用于严格的编译期检查、不可达代码、以及保证联合类型逻辑完备性。”

🐯any

any 会关闭所有类型检查,是 TS 世界的“丧失类型安全”。适用于以下严格使用场景(只有这些情况能用)

  1. 渐进式迁移 JS → TS,需要暂时兜底
let temp: any = legacyLib.getData();
  1. 类型真的无法确定(第三方库,动态字段特别复杂)

比如老旧 SDK,或者大量动态 key。

  1. 需要与 JS 环境交互(特殊全局变量、动态属性)

例如 window 全局写入自定义字段。

  1. 为了避免过度复杂的类型推导(工程权衡)

例如复杂泛型导致 IDE 卡顿。

any 是一种工程妥协,不是类型,它是关闭类型检查的开关。只有在迁移、兼容、动态环境时才应该使用。

三者关系

  • unknown 是安全的顶类型(不信任输入 → 先 unknown)
  • never 是不可能发生的底类型(穷尽检查、异常、严格逻辑)
  • any 是逃生舱(完全关闭类型检查,只在必要时使用)

unknown 与 any 是对立的:一个提高安全,一个降低安全;

never 则代表类型系统的底部,是逻辑完备性检查的核心工具。

Partial & Pick & infer

Partial 和 Pick 是典型的映射类型,用来‘改造’对象键;

infer 是在条件类型中做‘反向推断’,固定在提取某些类型信息的场景里。

这三个是 TypeScript 类型系统里最基础也最常用的构建块,理解它们能写出真正类型安全的库级代码。”

🍒Partial

Partial<T> 会把类型 T 的所有属性变成可选属性。

它是一个典型的「映射类型」,通过遍历 keyof T,把每个键的属性加上 ? 修饰符。

type MyPartial<T> = {
  [P in keyof T]?: T[P];
};

工程使用场景:

  • 表单场景:编辑时部分字段可选
  • DTO/Object patch:只更新传入字段
  • 配置对象:允许用户覆盖默认配置

生产里基本把所有配置类型都写成 Partial 形式,减少冗余类型声明,也避免用户必须传所有属性。

🍑Pick

Pick<T, K> 从类型 T 中挑选部分键 K,生成一个子类型。

它同样是映射类型,只是遍历 K 而不是 keyof T

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

工程使用场景

  • API 拆分:只需要某些字段
  • React Props 提取
  • 二次封装组件时抽取部分属性

Pick 本质上就是结构化编程里的投影操作,非常常用。

🍇infer

infer 是 TypeScript 在条件类型里用于声明一个待推断的类型变量
它让 TypeScript 能“从类型中反向提取信息”。

  • 只能在条件类型 T extends X ? ... : ...true 分支里使用
  • 相当于告诉 TS:“这里有个未知类型 R,你帮我推断它是什么”
  • 最典型的例子:从函数类型中提取返回值

手写 ReturnType

type MyReturnType<F> = 
  F extends (...args: any[]) => infer R 
    ? R 
    : never;

常见 infer 使用:

  • 提取 Promise 内部类型
type Awaited<T> = T extends Promise<infer R> ? R : T;
  • 提取数组元素类型
type ElementOf<T> = T extends (infer U)[] ? U : T;

工程使用场景

  • 自动根据 API 类型生成 Response 类型
  • 根据函数库自动推断返回值
  • 在前端状态管理中自动生成 Action 类型
  • 在复杂表单里从 DTO 推类型

infer 是 TypeScript 高阶类型里很核心的机制,它让 TypeScript 能像编译器一样进行模式匹配。