跳到主要内容

TypeScript 速查表,100+ 段地道代码,覆盖类型/泛型/工具类型/类型收窄

TypeScript 速查表,100+ 段代码涵盖类型/泛型/工具类型/类型收窄/异步模式。

  • 本地处理
  • 分类 开发运维
  • 适合 格式化、校验、压缩或检查和代码相关的文本。
169
基础类型 (20)

string 字符串类型

let name: string = "Lei";
let greet: string = `Hello, ${name}`;   // 模板字符串
let multi: string = `line 1
line 2`;                                 // 多行

string 原始类型接受单引号、双引号和反引号模板字面量。要插值或多行就用反引号模板字符串。

number 整数浮点合一

let count: number = 42;
let pi: number = 3.14;
let hex: number = 0xff;          // 255
let bin: number = 0b1010;        // 10
let big: bigint = 9007199254740993n;   // 超过 Number.MAX_SAFE_INTEGER 用 bigint

TypeScript 只有一个 number(IEEE 754)涵盖整数和浮点。超过 2^53 − 1 用 bigint,字面量加 n 后缀。

boolean 布尔类型

let done: boolean = false;
let ok: boolean = !!getValue();   // 双感叹号转 boolean

只有 true 和 false。把任何值转 boolean 用双感叹号 !!value 这个老套路。

null 与 undefined 两种空

let a: null = null;
let b: undefined = undefined;
let c: string | null = getValue();   // 可能为 null
let d: string | undefined;           // 默认 undefined

// strictNullChecks 开了之后必须显式处理
if (c !== null) {
  c.toUpperCase();   // ✅ 安全
}

null 是显式空值,undefined 是"还没赋值"。开了 strictNullChecks 后(务必开),两者都不能赋给其他类型,必须显式联合。

any 逃生口

let x: any = "hello";
x = 42;        // 允许
x = { foo: 1 };// 允许
x.bar.baz();   // 不报错,运行时炸

// ❌ 几乎从不该用,优先 unknown

any 把类型检查全关了。几乎从不是对的选择,用 unknown 替代。只在从 JS 迁移的边界可以用。

unknown 安全版 any

let x: unknown = JSON.parse(input);

// ❌ 直接用会报错
// x.toUpperCase();

// ✅ 必须先收窄
if (typeof x === "string") {
  x.toUpperCase();   // 这里 x 已经是 string
}

unknown 接受任意值但拿到之后什么也干不了,必须先收窄。解析后的 JSON / 反序列化数据 / 第三方回调都该用它。

never 不可能存在的类型

function fail(msg: string): never {
  throw new Error(msg);   // 永远不返回
}

function loop(): never {
  while (true) {}         // 死循环也是 never
}

// 用在穷举检查
const _exhaustive: never = value;

never 表示永远不出现的值,抛异常的函数、死循环、联合里没剩下的分支。用 let _: never = value 在可辨识联合上做穷举检查。

void 无意义返回值

function log(msg: string): void {
  console.log(msg);
  // 隐式 return undefined
}

// 注意:void 返回类型的函数 PARAM 可以返回任意值
const cb: () => void = () => 42;   // ✅ 合法,值会被丢弃

void 表示"调用方不该用返回值"。坑:参数位置上的 () => void 接受返回任意值的函数,返回值会被丢弃。

字面量类型:值即类型

let dir: "up" | "down" | "left" | "right";
dir = "up";       // ✅
// dir = "north"; // ❌ 不在联合里

let status: 200 | 404 | 500;
let flag: true;   // 唯一一个 true

字符串/数字/布尔字面量都能当类型,变量必须等于联合里某个字面量。可辨识联合和查表常量的基石。

Array<T> 与 T[] 两种数组语法

let xs: number[] = [1, 2, 3];
let ys: Array<number> = [1, 2, 3];   // 同义

// 嵌套时 T[] 更清爽
let grid: number[][] = [[1, 2], [3, 4]];
let grid2: Array<Array<number>> = [[1, 2], [3, 4]];

T[] 和 Array<T> 完全等价。简单类型用 T[],复杂联合用 Array<T> 更清楚(Array<string | number>)。

readonly 只读数组

let xs: readonly number[] = [1, 2, 3];
// xs.push(4);   ❌ Property 'push' does not exist
// xs[0] = 9;    ❌ Index signature is readonly

// 等价语法
let ys: ReadonlyArray<number> = [1, 2, 3];

readonly T[] 去掉所有 mutating 方法(push、pop、splice)也不能下标赋值。函数参数标 readonly 表示"我不会改它"。

元组类型:定长不同类型

let pair: [string, number] = ["age", 30];
let trio: [string, number, boolean] = ["x", 1, true];

// 命名元组(4.0+),仅文档用途
let coord: [x: number, y: number] = [10, 20];

// 可选元组元素
let opt: [string, number?] = ["x"];

// rest 元组元素
let rest: [string, ...number[]] = ["x", 1, 2, 3];

元组是定长数组,每个位置类型独立。命名标签(4.0+)只是文档用途。[string, number?] 允许末尾可选;[T, ...U[]] 允许尾部 rest。

object 非原始类型

let o: object = { x: 1 };
o = [1, 2, 3];    // ✅ 数组也是 object
o = () => {};     // ✅ 函数也是 object
// o = "hello";   // ❌ 字符串是 primitive

// ⚠️ object 上几乎什么也拿不到,你要的多半是 Record<string, unknown>
let m: Record<string, unknown> = { a: 1, b: "x" };

object 表示"不是原始类型的任何东西",数组、函数、普通对象都算。上面拿不到任何属性;想要类型化字典用 Record<string, unknown>。

enum 运行时 + 类型

enum Color { Red, Green, Blue }   // 0, 1, 2

let c: Color = Color.Red;     // 0
Color[0];                       // "Red" (反查)

enum Status { Ok = "OK", Err = "ERR" }   // 字符串 enum

enum 生成运行时对象,数字 enum 双向映射(数字 ↔ 名字),字符串 enum 单向。新代码通常优先字符串字面量联合。

symbol 唯一属性键

const id: symbol = Symbol("id");
const a = Symbol("x");
const b = Symbol("x");
a === b;   // false,每个 Symbol 都唯一

// unique symbol 才能当类型层面的常量键
const KEY: unique symbol = Symbol();
interface Box { [KEY]: number }

每次 Symbol() 调用都返回唯一值,所以相同描述的两个 symbol 也不相等。只有 const x: unique symbol 才能在类型里当计算属性键。

const enum 内联零运行时

const enum Dir { Up, Down }

const d = Dir.Up;
// 编译输出直接内联为:  const d = 0;
// 不生成 Dir 这个运行时对象

// ⚠️ 与 isolatedModules / 单文件转译(Babel、esbuild)冲突

const enum 在编译期被擦除,成员直接内联成字面量,不生成运行时对象。它与 isolatedModules 以及 Babel、esbuild 这类单文件转译器冲突。

函数类型标注

let add: (a: number, b: number) => number;
add = (x, y) => x + y;   // 参数类型自动推断

// 类型别名形式
type BinOp = (a: number, b: number) => number;
const mul: BinOp = (a, b) => a * b;

函数类型 (a: T, b: U) => R 描述参数和返回值。把符合的箭头函数赋给它时,参数类型按上下文自动推断,不用再标一遍。

可选参数与默认参数

function greet(name: string, greeting?: string) {
  return `${greeting ?? "Hi"}, ${name}`;   // greeting: string | undefined
}

function greet2(name: string, greeting = "Hi") {
  return `${greeting}, ${name}`;   // greeting: string,永远有值
}

p?: T 让参数变成 T | undefined,你得处理缺失的情况。默认值 p = value 则从类型里去掉 undefined,省略实参时自动填上。

函数重载

function len(x: string): number;
function len(x: any[]): number;
function len(x: string | any[]): number {
  return x.length;
}

len("abc");     // ✅ number
len([1, 2, 3]); // ✅ number

重载签名在一个实现之上声明多种调用形状。调用方只能看到重载签名,实现签名对外隐藏,且必须兼容所有重载。

this 参数标注

interface Counter { count: number }

function inc(this: Counter, by: number) {
  this.count += by;   // this 已知是 Counter
}

const c: Counter = { count: 0 };
inc.call(c, 5);   // ✅ this 类型被检查

名为 this 的伪首参用来标注接收者,它不出现在实际参数列表里。之后编译器会检查 call / apply / bind 传入的 this 是否兼容。

interface / type (12)

interface 对象形状

interface User {
  id: number;
  name: string;
  email?: string;        // 可选属性
  readonly createdAt: Date;   // 只读
}

const u: User = { id: 1, name: "Lei", createdAt: new Date() };

对象形状用 interface。? 标可选,readonly 禁止赋值。日常建模实体和 props 的首选。

type 别名:包括联合等一切

type ID = string | number;
type Point = { x: number; y: number };
type Callback = (err: Error | null, data?: string) => void;
type Tuple = [string, number];

type 别名给任意类型起名,联合、元组、函数、原始类型。interface 做不了联合,type 可以。

interface extends 继承扩展

interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

// 多继承
interface Cat extends Animal, Pet {
  livesLeft: number;
}

interface 可继承一个或多个 interface。子接口拿到所有父接口属性加自己的。属性冲突直接在声明处报错。

type 交叉 & 合并形状

type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;   // { name: string; age: number }

const p: Person = { name: "Lei", age: 30 };

type 别名用交叉 & 合并形状,等价于 interface extends,但适用任意类型,不限于 interface。

索引签名:动态 key

interface StringMap {
  [key: string]: string;
}

const m: StringMap = {};
m["lang"] = "zh";
m["theme"] = "dark";

// 同时有已知键 + 动态键
interface Config {
  apiUrl: string;
  [key: string]: string;
}

索引签名让你给类似字典的对象写动态 key 类型。所有已知键的值类型必须与索引签名一致。

调用签名:可被调用的 interface

interface Greeter {
  (name: string): string;             // 可调用
  greeting: string;                    // 还有属性
}

const g: Greeter = ((name: string) => `${g.greeting}, ${name}`) as Greeter;
g.greeting = "Hello";
g("Lei");   // "Hello, Lei"

interface 可声明调用签名,描述一个既是函数又有属性的对象。给"函数对象"(带 displayName 的 React FC 等)建模很好用。

interface vs type 怎么选

// ✅ 对象形状要被 extend / implement → interface
interface User { id: number; name: string }

// ✅ 联合 / 元组 / 映射 / 条件 → type
type ID = string | number;
type Tuple = [string, number];
type Keys<T> = keyof T;

经验法则:要被 extend / implement 的对象形状用 interface;联合 / 元组 / 计算出来的形状用 type。库的公共 API 倾向 interface,内部计算类型倾向 type。

构造签名 可被 new 的 interface

interface UserCtor {
  new (id: number, name: string): { id: number; name: string };
}

function create(Ctor: UserCtor, id: number, name: string) {
  return new Ctor(id, name);
}

new (...): T 签名描述构造函数类型,让值可以配 new 使用。工厂函数接收一个类引用再去实例化时常用。

混合类型 可调用且可索引

interface Counter {
  (start: number): string;   // 可调用
  interval: number;           // 属性
  reset(): void;              // 方法
}

function make(): Counter {
  const c = ((start: number) => String(start)) as Counter;
  c.interval = 123;
  c.reset = () => {};
  return c;
}

一个 interface 可同时含调用签名、属性和方法,建模函数对象。jQuery 那种既能调用又挂了一堆命名空间方法的 API 是经典例子。

class 用 implements 实现 interface

interface Serializable {
  serialize(): string;
}

class User implements Serializable {
  constructor(public id: number) {}
  serialize() { return JSON.stringify({ id: this.id }); }
}

class C implements I 让编译器校验该类拥有 I 的每个成员。这只是结构检查,不像 extends 那样继承实现。

递归 type 别名

type Json =
  | string
  | number
  | boolean
  | null
  | Json[]
  | { [key: string]: Json };

const data: Json = { a: 1, b: [true, null, { c: "x" }] };

type 别名可以引用自身,用来建模 JSON、树、嵌套菜单这类递归结构。联合里每个分支都能各自递归。

可选方法与可选属性

interface Plugin {
  name: string;
  setup?(): void;              // 可选方法
  teardown?: () => void;       // 可选属性形式的方法
}

const p: Plugin = { name: "x" };
p.setup?.();      // 可选链调用
p.teardown?.();

m?(): void 和 m?: () => void 都让方法可选,调用时走 obj.m?.()。属性形式在 strictFunctionTypes 下逆变,简写形式则双变。

联合 / 交叉 (9)

联合 |:A 或 B

type StringOrNumber = string | number;

function format(x: string | number): string {
  if (typeof x === "string") return x.toUpperCase();
  return x.toFixed(2);
}

联合 A | B 接受任一种。除非先收窄,否则只能调用两边都有的方法/属性。

交叉 &:A 且 B

type WithId = { id: string };
type WithName = { name: string };
type Entity = WithId & WithName;   // 同时有 id 和 name

const e: Entity = { id: "a1", name: "Lei" };

交叉 A & B 要求同时拥有 A 和 B 的所有属性。组合能力(capability)时常用。

可辨识联合:带标签的变体

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rect"; w: number; h: number }
  | { kind: "triangle"; base: number; h: number };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle":   return Math.PI * s.radius ** 2;
    case "rect":     return s.w * s.h;
    case "triangle": return 0.5 * s.base * s.h;
  }
}

共同的判别字段(这里是 kind)让联合可通过 switch 收窄。建模变体数据最可靠的套路。

never 穷举检查

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2;
    case "rect":   return s.w * s.h;
    default:
      const _exhaustive: never = s;   // ✅ 漏掉一个分支这里直接报错
      throw new Error(`Unknown shape: ${_exhaustive}`);
  }
}

把没处理的值赋给 never 类型,少一个分支就编译报错,专治"加了新变体忘改 switch"的 bug。

字面量联合:轻量 enum

type Status = "idle" | "loading" | "success" | "error";

let s: Status = "idle";
s = "loading";       // ✅
// s = "fetching";   // ❌ not in union

字符串字面量联合是 enum 的现代替代,零运行时成本、自动补全完美、能直接 JSON 序列化。

联合上的可选链 ?.

type Maybe = { profile?: { name?: string } } | null;

function getName(u: Maybe): string | undefined {
  return u?.profile?.name;   // 任一环为空就短路返回 undefined
}

可选链 ?. 在左侧为 null 或 undefined 时立即短路返回 undefined。结果类型一定带上 undefined,避免你忘了处理空值。

空值合并 ?? 与 ||

function port(input?: number) {
  const a = input ?? 8080;   // 只在 null/undefined 时用默认
  const b = input || 8080;   // 0 也会被替换掉!
  return [a, b];
}

port(0);   // [0, 8080]

?? 只在左侧是 null 或 undefined 时才用默认值,而 || 连 0、""、false 也一并替换。数值和布尔默认值用 ??,免得把合法的假值也吞掉。

函数类型的联合

type Handler = ((e: string) => void) | ((e: number) => void);

// ⚠️ 调用联合函数时,参数类型取交集(never)
declare const h: Handler;
// h("x");   ❌ 参数被推为 string & number = never

// 多数时候你想要的是单个带联合参数的函数:
type Better = (e: string | number) => void;

调用函数类型的联合时,实参要同时满足所有成员,于是参数被压成交集(常是 never)。多数情况下你真正想要的是一个参数为联合类型的单函数。

按 payload 收窄可辨识联合

type Result<T> =
  | { status: "ok"; data: T }
  | { status: "err"; message: string };

function unwrap<T>(r: Result<T>): T {
  if (r.status === "ok") return r.data;   // r.data 可用
  throw new Error(r.message);              // r.message 可用
}

检查判别字段(这里是 status)会收窄联合,于是分支专属的 payload 变得可访问。这是类型化 Result 和远程数据状态机的主干。

泛型 (14)

泛型函数:单个类型参数

function identity<T>(x: T): T {
  return x;
}

const s = identity("hello");   // T 推断为 string
const n = identity(42);        // T 推断为 number
const arr = identity<number[]>([1, 2, 3]);   // 显式传 T

类型参数 <T> 让函数适用任意类型,同时保留输入输出的类型关系。T 一般推断,推断不出来才显式传。

多个类型参数

function pair<A, B>(a: A, b: B): [A, B] {
  return [a, b];
}

const p = pair("lei", 30);   // [string, number]

function map<T, U>(xs: T[], fn: (x: T) => U): U[] {
  return xs.map(fn);
}

多个类型参数维持多边关系。map<T, U> 把输入数组元素类型和回调参数挂钩,回调返回类型决定输出数组元素类型。

extends 泛型约束

function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}

longest("abc", "ab");          // ✅ string 有 length
longest([1, 2, 3], [1]);       // ✅ array 有 length
// longest(42, 99);            // ❌ number 没有 length

T extends Shape 限制 T 必须符合 Shape,函数体内可以用约束里的属性。比"需要某个属性"用 any 强多了。

泛型默认参数

interface ApiResponse<T = unknown> {
  data: T;
  status: number;
}

const a: ApiResponse = { data: "?", status: 200 };           // T = unknown
const b: ApiResponse<User> = { data: { id: 1 } as User, status: 200 };

默认 <T = X> 让调用方可省略类型参数走默认值。框架代码常见(React.Component<P = {}, S = {}>)。

keyof 泛型约束

function pluck<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 1, name: "Lei" };
pluck(user, "id");      // number
pluck(user, "name");    // string
// pluck(user, "age");  // ❌ "age" 不是 user 的 key

K extends keyof T 约束 K 必须是 T 的某个属性名,返回 T[K] 给出精确的属性类型。lodash 的 _.pick 类型安全就靠它。

泛型 type 别名

type Result<T, E = Error> =
  | { ok: true; value: T }
  | { ok: false; error: E };

const r: Result<number> = { ok: true, value: 42 };

type 别名也可以带类型参数。Result wrapper、容器类型、计算出来的形状常用。

泛型 interface

interface Cache<T> {
  get(key: string): T | undefined;
  set(key: string, value: T): void;
}

const userCache: Cache<User> = makeCache<User>();

interface 也可以带类型参数。实例化时定型,Cache<User> 的 get 返回 User | undefined。

泛型 React 组件

interface ListProps<T> {
  items: T[];
  render: (item: T) => React.ReactNode;
}

function List<T>({ items, render }: ListProps<T>) {
  return <ul>{items.map((it, i) => <li key={i}>{render(it)}</li>)}</ul>;
}

<List items={users} render={(u) => u.name} />   // T 自动推断 User

泛型组件把 item 类型传到所有 props,render 拿到的是真正的 T,不是 any。带类型的 List / Table / Select 组件标准套路。

泛型 class

class Stack<T> {
  private items: T[] = [];
  push(x: T): void { this.items.push(x); }
  pop(): T | undefined { return this.items.pop(); }
  peek(): T | undefined { return this.items[this.items.length - 1]; }
}

const s = new Stack<number>();
s.push(1);
s.push(2);
s.pop();   // number | undefined

class 也能带类型参数。new Stack<number>() 时定型,所有方法都用这个 T。

const 类型参数(5.0+)

function asTuple<const T extends readonly unknown[]>(t: T): T {
  return t;
}

const a = asTuple([1, 2, 3]);   // readonly [1, 2, 3],不是 number[]
// const T 让推断像加了 as const 一样窄

const 类型参数(<const T>)从实参推断出最窄的字面量类型,效果如同调用方写了 as const。它避免把元组放宽成数组、字面量放宽成基础类型。

多重约束泛型

function merge<T extends object, U extends object>(a: T, u: U): T & U {
  return { ...a, ...u };
}

const r = merge({ id: 1 }, { name: "Lei" });
// r: { id: number } & { name: string }

每个类型参数都能带自己的 extends 约束,返回类型可用 & 把它们合起来。类型化的 Object.assign 风格合并就靠这个保留两边形状。

泛型默认值引用前一个参数

interface Reducer<S, A = { type: string }> {
  (state: S, action: A): S;
}

// A 默认 { type: string },也可显式传更精确的 action 联合
type CountReducer = Reducer<number>;

靠后的类型参数默认值可引用靠前的参数,比如 <S, A = S[]>。框架签名用它让调用方只填自己真正关心的参数。

约束到元组形状的泛型

function first<T extends readonly [unknown, ...unknown[]]>(t: T): T[0] {
  return t[0];
}

first([1, "a", true]);   // 1,类型 number
// first([]);            ❌ 空元组不满足约束

约束成 readonly [unknown, ...unknown[]] 要求至少一个元素,于是 t[0] 取值可证安全。空数组在调用点直接被编译器拒绝。

泛型柯里化

function prop<K extends string>(key: K) {
  return <T extends Record<K, unknown>>(obj: T): T[K] => obj[key];
}

const getName = prop("name");
getName({ name: "Lei", age: 30 });   // string

从外层函数返回一个泛型箭头,把一个类型参数带进内层作用域。内层函数每次调用仍重新推断 T,保持 helper 可复用。

工具类型 (22)

Partial<T> 全部属性可选

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

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

function update(id: number, patch: Partial<User>) { /* ... */ }
update(1, { name: "Lei" });   // ✅ 只传一个属性

Partial<T> 把每个属性变成可选。最常见用途:PATCH 风格的更新函数接受部分字段。

Required<T> 全部属性必填

interface Config { host?: string; port?: number }

type FullConfig = Required<Config>;
// { host: string; port: number }

function start(c: Required<Config>) { /* ... */ }

Required<T> 去掉所有 ? 可选标记。配置带默认值填充后想断言"全设好了"时用。

Readonly<T> 冻结全部属性

interface User { id: number; name: string }
type FrozenUser = Readonly<User>;

const u: FrozenUser = { id: 1, name: "Lei" };
// u.name = "Han";   // ❌ readonly

Readonly<T> 给每个属性加 readonly。编译期冻结,零运行时成本。要深度冻结需要递归版本。

Pick<T, K> 挑出指定属性

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

type PublicUser = Pick<User, "id" | "name" | "email">;
// { id: number; name: string; email: string }

Pick<T, K> 只挑指定 key 构造新类型。视图模型 / DTO 从更大的实体派生时用。

Omit<T, K> 排除指定属性

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

type SafeUser = Omit<User, "password">;
// { id: number; name: string }

Omit<T, K> 是 Pick 的反操作,除了列出的 key 全保留。绝大多数字段都要时比 Pick 简洁。

Record<K, V> 带类型字典

type Role = "admin" | "user" | "guest";

const permissions: Record<Role, string[]> = {
  admin: ["read", "write", "delete"],
  user: ["read", "write"],
  guest: ["read"],
};

Record<K, V> 等价于 { [P in K]: V },key 是 K,value 是 V。K 是字面量联合时编译器会校验每个 key 都被填上。

Exclude<T, U> 过滤联合

type Status = "idle" | "loading" | "success" | "error";

type Resolved = Exclude<Status, "idle" | "loading">;
// "success" | "error"

Exclude<T, U> 从 T 里去掉能赋给 U 的成员,对联合做差集。

Extract<T, U> 取联合交集

type All = string | number | boolean | null;

type Numerics = Extract<All, number | bigint>;
// number

Extract<T, U> 只保留 T 里能赋给 U 的成员,对联合取交集。

NonNullable<T> 去掉 null/undefined

type Maybe = string | number | null | undefined;

type Real = NonNullable<Maybe>;
// string | number

NonNullable<T> 同时去掉 null 和 undefined。用 x != null 收窄后常用。

ReturnType<F> 抽取返回类型

function getUser() {
  return { id: 1, name: "Lei", email: "x@y.z" };
}

type User = ReturnType<typeof getUser>;
// { id: number; name: string; email: string }

ReturnType<F> 抽出函数类型的返回值。配 typeof fn 用,从工厂函数推导出实体类型,不用重复写形状。

Parameters<F> 抽取参数元组

function login(user: string, pwd: string, remember?: boolean) { /* ... */ }

type LoginArgs = Parameters<typeof login>;
// [user: string, pwd: string, remember?: boolean | undefined]

Parameters<F> 返回函数参数类型组成的元组。包装函数需要原样转发参数列表时常用。

Awaited<T> 解开 Promise

type T1 = Awaited<Promise<string>>;        // string
type T2 = Awaited<Promise<Promise<number>>>;  // number  (递归解)
type T3 = Awaited<number>;                    // number  (不是 Promise 就原样)

Awaited<T> 递归解开嵌套 Promise。4.5+ 后是类型系统里 await 语义的官方实现。

ConstructorParameters<C> 抽取构造参数

class User {
  constructor(public id: number, public name: string) {}
}

type Args = ConstructorParameters<typeof User>;
// [id: number, name: string]

ConstructorParameters<C> 抽出构造函数的参数元组。工厂函数想完全镜像 class constructor 时用。

InstanceType<C> new 后的类型

class User { /* ... */ }

type U = InstanceType<typeof User>;
// User

InstanceType<C> 给出 new C() 的类型。主要配 mixin 和 typeof Class 引用实例类型时用。

Uppercase / Lowercase / Capitalize 字符串类型操作

type H = Uppercase<"hello">;     // "HELLO"
type W = Lowercase<"WORLD">;     // "world"
type T = Capitalize<"foo">;      // "Foo"
type U = Uncapitalize<"Bar">;    // "bar"

内置 intrinsic 类型,在类型层做字符串字面量变换。构造带类型的事件名、CSS-in-JS key 时用。

OmitThisParameter<F> 去掉 this

function fn(this: { x: number }, y: number) {
  return this.x + y;
}

type Plain = OmitThisParameter<typeof fn>;
// (y: number) => number

OmitThisParameter<F> 从函数类型里去掉 this 参数,留下纯粹的可调用签名。bind 掉接收者之后建模常用。

ThisParameterType<F> 抽取 this

function fn(this: { x: number }, y: number) {
  return this.x + y;
}

type Self = ThisParameterType<typeof fn>;
// { x: number }

ThisParameterType<F> 抽出函数声明的 this 参数类型。包装或重绑方法时配 OmitThisParameter 用。

NoInfer<T>(5.4+)阻止推断

function paint<C extends string>(
  palette: C[],
  fallback: NoInfer<C>,
) { /* ... */ }

paint(["red", "blue"], "red");    // ✅
// paint(["red", "blue"], "x");   ❌ "x" 不在 palette 推断出的 C 里

NoInfer<T> 告诉编译器推断类型参数时别参考这个位置。于是 C 由第一个参数定死,第二个参数只用来对照校验。

Record + Partial 稀疏字典

type Lang = "en" | "zh" | "ja";

type Translations = Partial<Record<Lang, string>>;
const t: Translations = { en: "Hello" };   // zh / ja 可缺省

把 Record<K, V> 套进 Partial,得到每个 key 都可选的字典,可只填已知键集合的一部分。部分 i18n 词包和功能开关常用。

Pick + Required 选择性强制必填

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

type WithId = User & Required<Pick<User, "id">>;
// id 必填,name / email 仍可选

const u: WithId = { id: 1 };   // ✅

把类型与 Required<Pick<T, K>> 交叉,只强制选中的键必填,其余仍可选。"这个字段现在保证存在"的视图模型很好用。

Mutable<T> 去掉 readonly(自定义)

type Mutable<T> = { -readonly [K in keyof T]: T[K] };

const frozen = { x: 1, y: 2 } as const;   // readonly { x: 1; y: 2 }
type Editable = Mutable<typeof frozen>;
// { x: 1; y: 2 },可改

TypeScript 没有内置 Mutable,去除只读的标准写法就是 -readonly 映射修饰符。常配那些先 as const、之后又要改的数据用。

T[number] 元组转联合

const ROLES = ["admin", "user", "guest"] as const;

type Role = typeof ROLES[number];
// "admin" | "user" | "guest"

用 [number] 索引只读元组类型,得到所有元素类型的联合。这是从单一事实数组派生字符串字面量联合的标准技巧。

类型收窄 (13)

typeof 收窄

function format(x: string | number): string {
  if (typeof x === "string") {
    return x.toUpperCase();   // x: string
  }
  return x.toFixed(2);        // x: number
}

typeof 返回 "string" | "number" | "boolean" | "object" | "function" | "undefined" | "symbol" | "bigint"。编译器在分支里自动收窄。

instanceof 收窄

class HttpError extends Error { status: number = 500 }
class NetError  extends Error { code: string = "ECONN" }

function handle(e: HttpError | NetError) {
  if (e instanceof HttpError) {
    console.log(e.status);   // e: HttpError
  } else {
    console.log(e.code);     // e: NetError
  }
}

instanceof Class 收窄到 class 类型。任何构造函数都行,内置的 Error / Date 和用户类都可以。

in 操作符收窄

type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(a: Fish | Bird) {
  if ("swim" in a) {
    a.swim();   // a: Fish
  } else {
    a.fly();    // a: Bird
  }
}

"key" in obj 收窄到联合里有这个 key 的成员。不想加判别字段时比可辨识联合更轻量。

相等收窄

function example(x: string | null) {
  if (x !== null) {
    x.toUpperCase();   // x: string
  }
}

function check(a: "a" | "b", b: "b" | "c") {
  if (a === b) {
    // 这里 a 和 b 都是 "b",唯一公共值
  }
}

=== 和 !== 收窄联合成员。两个联合互相比较时,两边都收窄到公共成员。

真值收窄

function example(x: string | null | undefined) {
  if (x) {
    x.toUpperCase();   // x: string (null / undefined / "" 都过滤了)
  }
}

// 注意:空字符串也会被过滤
function trap(x: string | null) {
  if (x) {
    // x: string,但 "" 也被排除了!
  }
}

真值判断 if (x) 过滤 null、undefined、0、""、NaN、false。坑:空字符串也算 false,往往不是你想要的。

用户自定义类型守卫 is

function isString(x: unknown): x is string {
  return typeof x === "string";
}

function example(x: unknown) {
  if (isString(x)) {
    x.toUpperCase();   // x: string
  }
}

返回类型写 x is T 告诉编译器"此函数返回 true 当且仅当 x 是 T"。教类型系统理解自定义检查的途径。

断言函数 asserts

function assertString(x: unknown): asserts x is string {
  if (typeof x !== "string") throw new Error("not string");
}

function example(x: unknown) {
  assertString(x);
  x.toUpperCase();   // x: string  (assert 之后整段都是 string)
}

asserts x is T 在调用之后收窄(不是在 if 分支里)。想要抛异常而不是返回 boolean 的守卫时用。

可辨识联合 switch 完整模式

type Action =
  | { type: "ADD"; payload: number }
  | { type: "RESET" }
  | { type: "SET"; payload: number };

function reduce(state: number, a: Action): number {
  switch (a.type) {
    case "ADD":   return state + a.payload;
    case "RESET": return 0;
    case "SET":   return a.payload;
    default:
      const _: never = a;   // 漏一个分支编译报错
      return state;
  }
}

Redux 风格的可辨识联合 reducer 完整模式,带穷举检查。新加一种 action 类型不处理就编译报错。

Array.isArray 收窄

function flatten(x: string | string[]): string {
  if (Array.isArray(x)) {
    return x.join(", ");   // x: string[]
  }
  return x;                 // x: string
}

Array.isArray(x) 是内置类型守卫,把 T | T[] 收窄到数组分支。接受"一个或多个"参数时最可靠的写法。

按字面量属性判别

type Resp =
  | { ok: true; body: string }
  | { ok: false; code: number };

function read(r: Resp) {
  if (r.ok) return r.body;   // ok: true 分支
  return `error ${r.code}`; // ok: false 分支
}

布尔(或任意字面量)字段和字符串 kind 一样能当判别字段。检查 r.ok 就收窄了联合,不必专门加 tag 字符串。

用 const 断言辅助收窄

function handle(method: string) {
  const m = method.toUpperCase();
  if (m === "GET" || m === "POST") {
    const verb = m as "GET" | "POST";   // 明确收窄
    return verb;
  }
}

把放宽过的 string 与字面量比较后,用 as 断言把它锁回字面量联合供后续使用。最好一开始就标窄类型,但这招能救已经放宽的值。

赋值后的控制流收窄

let x: string | number;
x = "hello";
x.toUpperCase();   // x: string,赋值后立即收窄

x = 42;
x.toFixed(2);      // x: number

赋值会把变量在后续语句里收窄到该值的类型,即便声明类型是联合。这是你天天依赖却毫无察觉的控制流分析。

用局部常量收窄对象属性

function run(cfg: { value?: number }) {
  // cfg.value 每次访问可能被别处改,TS 不替你收窄
  const v = cfg.value;
  if (v !== undefined) {
    v.toFixed(2);   // ✅ 局部常量收窄成立
  }
}

对 obj.prop 的收窄可能被中间的调用打破,所以先拷进一个 const 再收窄它。局部绑定可证不变,守卫才稳。

映射类型 (11)

基础映射类型

type Stringify<T> = {
  [K in keyof T]: string;
};

interface User { id: number; age: number }
type UserStrings = Stringify<User>;
// { id: string; age: string }

{ [K in keyof T]: ... } 遍历 T 的 key 生成新类型。Partial、Required、Readonly 都是基于映射类型实现的。

带值变换的映射类型

type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

interface User { id: number; name: string }
type N = Nullable<User>;
// { id: number | null; name: string | null }

用 T[K] 引用原值类型再变换(这里联合 null)。"所有字段都允许 null"的包装常用。

4.1+ 用 as 重命名 key

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface User { id: number; name: string }
type UserGetters = Getters<User>;
// { getId: () => number; getName: () => string }

as 子句在映射时重命名 key。配合模板字面量类型,可从基类型生成 getter / setter / 事件名的形状。

用 as never 过滤 key

type PickStrings<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};

interface Mixed { name: string; age: number; role: string }
type Strings = PickStrings<Mixed>;
// { name: string; role: string }

把 key 映射到 never 就会从结果里删掉。配合条件类型,可按值类型过滤对象属性。

+ 与 - 修饰 optional / readonly

type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type Optional<T> = { [K in keyof T]+?: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] };

interface Frozen { readonly id: number; readonly name: string }
type Editable = Mutable<Frozen>;
// { id: number; name: string }

- 去掉 optional 或 readonly,+ 加上(默认)。内置 Required 和 Mutable 工具就靠这个机制实现。

DeepReadonly 映射 + 递归

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? T[K] extends Function
      ? T[K]
      : DeepReadonly<T[K]>
    : T[K];
};

interface Tree { left: Tree | null; value: number }
type FrozenTree = DeepReadonly<Tree>;
// 整棵树都是 readonly

递归映射类型把嵌套对象属性也冻起来。排除 Function(typeof function === "object")避免方法被改坏。

映射确保所有 key 在场

type FormValues<Fields extends string> = {
  [K in Fields]: string;
};

type LoginForm = FormValues<"email" | "password">;
// { email: string; password: string }

const f: LoginForm = { email: "x@y.z", password: "secret" };
// 漏 email 编译报错

遍历有限字符串联合要求每个 key 都在场。编译器校验完整性,表单 schema、权限表、路由表常用。

同态映射保留修饰符

type Clone<T> = { [K in keyof T]: T[K] };

interface User { readonly id: number; name?: string }
type Copy = Clone<User>;
// { readonly id: number; name?: string },readonly / ? 都保留

写成 [K in keyof T] 的映射类型是同态的,会自动复制每个属性的 readonly 和 ? 修饰符。这正是 Partial / Readonly 能保留原结构的原因。

映射联合实现 Record

type MyRecord<K extends keyof any, V> = {
  [P in K]: V;
};

type Flags = MyRecord<"a" | "b", boolean>;
// { a: boolean; b: boolean }

在 key 的联合上映射 [P in K] 正是内置 Record<K, V> 的定义方式。keyof any(string | number | symbol)是最宽的合法 key 约束。

映射值配条件类型查表

type Boxed<T> = {
  [K in keyof T]: T[K] extends string ? { text: T[K] } : { value: T[K] };
};

interface M { name: string; age: number }
type R = Boxed<M>;
// { name: { text: string }; age: { value: number } }

在映射类型里可对 T[K] 用条件类型分支,按每个值的类型分别变换。常用于按原类型把字段包进带标签的容器。

映射数组 / 元组

type Boxed<T> = { [K in keyof T]: { v: T[K] } };

type R = Boxed<[number, string]>;
// [{ v: number }, { v: string }],元组结构保留

在元组上映射会保持同长度的元组,逐位变换每个元素。能感知数组/元组的映射类型撑起了逐元素包装这类工具。

条件类型 (10)

基础条件类型 extends ? :

type IsString<T> = T extends string ? "yes" : "no";

type A = IsString<"hello">;   // "yes"
type B = IsString<42>;         // "no"
type C = IsString<string>;     // "yes"

T extends U ? X : Y 根据可赋值性选类型。所有"聪明"工具类型的底层机制。

分配律条件类型

type ToArray<T> = T extends any ? T[] : never;

type A = ToArray<string | number>;
// string[] | number[]  (而不是 (string | number)[])
//
// 联合传入裸类型参数时,条件类型会分配到每个成员

类型参数是"裸"的(就是 T,没包起来)时,条件类型对联合的每个成员分配。要阻止分配就包起来:[T] extends [U]。

包裹防止分配

type IsUnion<T, _T = T> = T extends any
  ? [Exclude<_T, T>] extends [never]
    ? false
    : true
  : never;

type A = IsUnion<string | number>;   // true
type B = IsUnion<string>;             // false

把类型参数包到元组里 [T] 阻止分配。用于判断"这是不是一个联合"的套路。

条件类型链

type TypeName<T> =
  T extends string ? "string" :
  T extends number ? "number" :
  T extends boolean ? "boolean" :
  T extends undefined ? "undefined" :
  T extends Function ? "function" :
  "object";

type A = TypeName<"hello">;   // "string"
type B = TypeName<() => void>;// "function"

条件类型链模拟 switch / 模式匹配。从上往下,先匹配的赢。

条件类型 + 约束

type NonEmpty<T extends string> = T extends "" ? never : T;

type A = NonEmpty<"hello">;   // "hello"
type B = NonEmpty<"">;         // never

function tag<T extends string>(s: NonEmpty<T>): T {
  return s;
}
// tag("");  ❌ "" 推断为 never,无法调用

泛型约束配条件类型在调用点验证输入形状。NonEmpty<""> 解析为 never,调用直接报错。

NonNullable 内置条件类型

// 内置实现:
type NonNullable<T> = T extends null | undefined ? never : T;

type A = NonNullable<string | null>;       // string
type B = NonNullable<string | undefined>;  // string
type C = NonNullable<null>;                // never

NonNullable 用分配律条件类型把 null/undefined 映射到 never。读内置工具类型实现是吃透条件类型的最快路径。

展开一层数组

type Flatten<T> = T extends (infer E)[] ? E : T;

type A = Flatten<string[]>;   // string
type B = Flatten<number>;     // number(不是数组就原样)

带 infer E 的条件类型在 T 是数组时剥掉一层,否则原样返回 T。标准库的 FlatArray 就是这个形状。

递归深度展平条件类型

type DeepFlatten<T> = T extends (infer E)[] ? DeepFlatten<E> : T;

type A = DeepFlatten<number[][][]>;   // number
type B = DeepFlatten<string>;         // string

在 infer E 分支里递归,可把任意深度的嵌套数组压到叶子类型。递归条件类型是类型层面的 while 循环。

泛型默认值里的条件类型

type Unbox<T, F = T extends Promise<infer U> ? U : T> = F;

type A = Unbox<Promise<number>>;   // number
type B = Unbox<string>;            // string

默认类型参数本身可以是一个从前置参数计算出来的条件类型。它让你对外暴露派生类型,同时保持公共签名简短。

条件类型区分函数与值

type Unwrap<T> = T extends () => infer R ? R : T;

type A = Unwrap<() => number>;   // number
type B = Unwrap<string>;          // string

推断无参函数的返回类型、否则回退到值本身,建模了"惰性或即时"输入。常用于既接受值又接受工厂函数的配置项。

infer 推断 (10)

infer 抽取函数返回类型

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

type R1 = MyReturnType<() => string>;            // string
type R2 = MyReturnType<(x: number) => boolean>;  // boolean

infer R 给你想抽出的类型位置起名字。内置的 ReturnType 就是这个套路。

infer 解开 Promise

type MyAwaited<T> = T extends Promise<infer U> ? U : T;

type A = MyAwaited<Promise<string>>;   // string
type B = MyAwaited<number>;             // number

在 Promise<...> 里用 infer 抽出 resolved 类型。内置 Awaited 还会递归处理嵌套 Promise。

infer 抽取数组元素类型

type Element<T> = T extends (infer E)[] ? E : never;

type A = Element<number[]>;            // number
type B = Element<Array<{ x: number }>>;// { x: number }

(infer E)[] 模式抽出数组元素类型。等价于数组上的 T[number] 查表写法。

infer 抽取元组头/尾

type Head<T extends any[]> = T extends [infer H, ...any[]] ? H : never;
type Tail<T extends any[]> = T extends [any, ...infer R] ? R : [];

type H = Head<[string, number, boolean]>;   // string
type T = Tail<[string, number, boolean]>;   // [number, boolean]

变长元组类型配 infer 可以拆元组,头、尾、最后一个、init。编译期 list 操作的基础。

infer 抽取首个函数参数

type FirstParam<F> = F extends (first: infer P, ...rest: any[]) => any ? P : never;

type A = FirstParam<(name: string, age: number) => void>;   // string

在参数列表第一个位置用 infer,剩下忽略。镜像内置 Parameters,但只要第一个。

infer 抽取元组最后一个

type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;

type A = Last<[1, 2, 3]>;   // 3
type B = Last<[]>;           // never

剩余模式 [...any[], infer L] 抓住元组最后一个元素。变长元组位置配 infer,可以不递归地读出头、尾、末元素。

infer 递归解 Promise

type Resolved<T> = T extends Promise<infer U> ? Resolved<U> : T;

type A = Resolved<Promise<Promise<number>>>;   // number
type B = Resolved<string>;                       // string

对推断出的 U 递归,可把任意层嵌套的 Promise 解到最终值,镜像内置 Awaited。非 Promise 分支终止递归。

infer 带约束(extends)

type FirstString<T> =
  T extends [infer S extends string, ...any[]] ? S : never;

type A = FirstString<["hi", 1, 2]>;   // "hi"
type B = FirstString<[1, 2]>;          // never

4.7 起可用 infer S extends C 给推断结果加约束,只有推断类型符合时分支才命中。省去再嵌一层条件类型去校验推断结果。

infer 抽取对象属性类型

type PropType<T, K extends string> =
  T extends { [P in K]: infer V } ? V : never;

type A = PropType<{ id: number; name: string }, "id">;   // number

把 infer V 放在属性位置,从匹配的对象形状里抽出该字段类型。它把索引访问 T[K] 推广成编译器可结构匹配的模式。

infer 抽取剩余参数元组

type RestArgs<F> =
  F extends (first: any, ...rest: infer R) => any ? R : never;

type A = RestArgs<(id: number, a: string, b: boolean) => void>;
// [a: string, b: boolean]

推断 ...rest: infer R 把第一个之后的所有参数抓成元组。包装函数固定首参、转发其余参数时常用。

模板字面量 (8)

模板字面量类型基础

type Greeting = `hello ${string}`;

const g1: Greeting = "hello world";   // ✅
const g2: Greeting = "hello there";   // ✅
// const g3: Greeting = "hi";         // ❌ 不以 "hello " 开头

模板字面量类型(4.1+)按模式约束字符串。嵌 string 让那段可变;嵌字面量联合就是有限集合。

与联合的笛卡尔积

type Lang = "en" | "zh";
type Kind = "button" | "link";

type Key = `${Kind}.${Lang}`;
// "button.en" | "button.zh" | "link.en" | "link.zh"

模板里嵌联合得到笛卡尔积。i18n key、事件名、CSS 类名常用。

模板字面量配映射类型

type EventNames<T> = {
  [K in keyof T as `on${Capitalize<string & K>}`]?: (value: T[K]) => void;
};

interface Form { name: string; age: number }
type FormHandlers = EventNames<Form>;
// { onName?: (v: string) => void; onAge?: (v: number) => void }

映射类型 + key 重命名 + 模板字面量组合,自动生成 handler 类型。类型安全事件 API 的底层套路。

配 infer 解析字符串

type Split<S extends string, D extends string> =
  S extends `${infer A}${D}${infer B}`
    ? [A, ...Split<B, D>]
    : [S];

type A = Split<"a,b,c,d", ",">;   // ["a", "b", "c", "d"]

递归模板字面量配 infer 可在编译期切字符串、解析路由、校验字符串形状。

模板字面量把联合转字符串

type Id = `user_${number}`;
const a: Id = "user_42";   // ✅
// const b: Id = "user_x"; ❌ 必须是数字

type Hex = `#${string}`;
const c: Hex = "#ff0000";  // ✅

模板类型里嵌 number 或 string 约束整体形状,同时让那一段自由。适合带前缀的 id 格式、十六进制颜色、加前缀的 key。

模板字面量内联 Uppercase

type Method = "get" | "post";
type Route = `${Uppercase<Method>} /api`;
// "GET /api" | "POST /api"

Uppercase 这类 intrinsic 字符串类型能直接嵌进模板字面量,在生成笛卡尔积时变换每个联合成员。HTTP 方法表、事件通道常用。

模板配 infer 抽取前缀

type RemovePrefix<S extends string, P extends string> =
  S extends `${P}${infer Rest}` ? Rest : S;

type A = RemovePrefix<"on:click", "on:">;   // "click"
type B = RemovePrefix<"click", "on:">;       // "click"

匹配 `${P}${infer Rest}` 去掉已知前缀并抓住剩余部分。可在类型层解析事件名、带命名空间的 key、CSS 变量前缀。

用分隔符 join 字符串元组

type Join<T extends string[], D extends string> =
  T extends [infer F extends string, ...infer R extends string[]]
    ? R extends []
      ? F
      : `${F}${D}${Join<R, D>}`
    : "";

type A = Join<["a", "b", "c"], "/">;   // "a/b/c"

递归模板字面量用分隔符把字符串元组连起来,是 Split 的类型层逆操作。空剩余分支避免末尾多出分隔符。

断言 (10)

as 类型断言(强转)

const el = document.getElementById("input") as HTMLInputElement;
el.value = "hello";   // ✅ 编译器信你说的是 input

// ❌ 不检查,错了运行时炸
const wrong = "hello" as unknown as number;

as Foo 是不检查的强转,编译器信你。只有当你真的知道得比编译器多时才用。绝不能当"关掉类型错误"的锤子。

as const 冻结字面量

const a = "hello";              // type: string
const b = "hello" as const;     // type: "hello"

const arr1 = [1, 2, 3];         // type: number[]
const arr2 = [1, 2, 3] as const;// type: readonly [1, 2, 3]

const obj = { x: 1, y: 2 } as const;
// type: { readonly x: 1; readonly y: 2 }

as const 把值冻到最窄类型,字符串保留字面量、数组变只读元组、对象深只读。查表常量和配置常用。

satisfies 校验但不放宽(4.9+)

type Config = Record<string, string | number>;

const config = {
  port: 8080,
  host: "localhost",
  protocol: "https",
} satisfies Config;

config.port;       // number  (推断保留)
config.protocol;   // "https" (字面量保留)

// ❌ 用 : Config 标注会放宽到 string | number 丢失字面量

satisfies 校验值符合类型但不放宽推断类型。配置对象想同时拿到编译期校验和窄字面量类型查表时的最佳选择。

非空断言 !

const el = document.getElementById("input")!;  // 断言不是 null
el.click();

// ❌ 滥用会埋雷
// 更安全:
const el2 = document.getElementById("input");
if (el2) el2.click();

后缀 ! 告诉编译器"我保证这不是 null/undefined"。DOM 查询是主要合法用例,即便如此,更推荐显式 null 检查。

尖括号断言:旧语法

const a = <string>someValue;       // 旧写法
const b = someValue as string;     // ✅ 推荐(JSX 不冲突)

老的 <Type>value 写法在 JSX 里冲突。TSX 文件必须用 as Type(其他地方也建议统一用 as 保持一致)。

通过 unknown 双重断言

// ❌ 直接断不让你过
// const n = "hello" as number;

// ✅ 经过 unknown 二段断言(编译器允许,但你自己要负责)
const n = "hello" as unknown as number;

// 真要这么干说明设计有问题,重新想想类型

源类型和目标类型重叠不够时 TS 拒绝直接断。as unknown as Foo 是逃生口,几乎一定意味着类型设计有问题,需要重新想。

satisfies 与 as const 合用

type Route = { path: string; auth: boolean };

const routes = {
  home: { path: "/", auth: false },
  admin: { path: "/admin", auth: true },
} as const satisfies Record<string, Route>;

routes.home.path;   // "/"(字面量保留)

as const satisfies T 串用,一步把值冻到最窄字面量类型并对照 T 校验。既拿到深只读字面量,又有形状检查。

对象字面量的 const 断言

function on<E extends string>(event: E) { return event; }

on("click");                 // E 推断为 string
on("click" as const);        // E 推断为 "click"

const ev = { type: "click" } as const;
ev.type;   // "click",不是 string

对字面量或对象用 as const,把属性冻到精确值并防止调用点放宽。这是把精确字面量传进泛型的轻量办法。

明确赋值断言 !

let value!: number;   // 告诉编译器:我保证用前会赋值

function init() { value = 42; }
init();
value.toFixed(2);   // ✅ 不再报 used before assigned

class C {
  ref!: HTMLElement;   // 类字段同理(由 DI / 生命周期赋值)
}

变量或类字段名后加 ! 是明确赋值断言,告诉编译器使用前一定会被赋值。常用于由依赖注入、生命周期钩子或测试 setup 赋值的字段。

const 断言传只读元组实参

function dispatch(...args: readonly [string, number]) {}

const payload = ["save", 1] as const;
dispatch(...payload);   // ✅ as const 让它是 readonly ["save", 1]
// 不加 as const 会推成 (string | number)[],spread 不匹配

把数组 spread 进元组参数,需要数组是定长元组类型,as const 正好给出。不加它数组会放宽成 T[],spread 对不上位置。

声明合并 (7)

interface 声明合并

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

// 自动合并:
// interface User { id: number; name: string }

const u: User = { id: 1, name: "Lei" };

同作用域两个同名 interface 自动合并。库通过这个机制让用户扩展类型。

模块增强

// my-app.d.ts
import "express";

declare module "express" {
  interface Request {
    user?: { id: number; name: string };
  }
}

// 现在 Express 的 Request 上有了 user 字段
app.use((req, res, next) => {
  req.user = { id: 1, name: "Lei" };
  next();
});

declare module "name" 重新打开外部模块加类型。给 Express 的 Request、Vue 的 ComponentCustomProperties 加字段的标准做法。

全局声明

// globals.d.ts
declare global {
  interface Window {
    myApp: { version: string };
  }

  var __DEV__: boolean;
}

export {};   // 重要:让这个文件成为 module

// 使用
window.myApp = { version: "1.0.0" };
if (__DEV__) console.log("dev mode");

module 里的 declare global { ... } 给全局作用域加类型。文件末尾 export {} 必加,让文件变成 module。构建期 define 和 Window 增强常用。

namespace 与函数合并

function area(r: number) { return Math.PI * r * r; }

namespace area {
  export const unit = "cm²";
}

area(2);        // 函数调用
area.unit;      // "cm²",挂在同名 namespace 上

函数与同名 namespace 合并,让你给函数挂上静态 helper 和常量。库就是这样在完整类型下同时暴露 fn 和 fn.helper。

enum 与 namespace 合并

enum Color { Red, Green, Blue }

namespace Color {
  export function hex(c: Color): string {
    return ["#f00", "#0f0", "#00f"][c];
  }
}

Color.hex(Color.Red);   // "#f00"

与 enum 同名的 namespace 会合并,让你给枚举对象挂上 helper 函数。enum 仍既是值又是类型,同时多了方法。

declare 环境声明

// 描述一个由 <script> 注入、没有类型的全局
declare const ANALYTICS_ID: string;
declare function track(event: string): void;

track("page_view");   // ✅ 有类型,无运行时代码

declare 为运行时存在但没有 TS 源码的东西(比如 script 注入的全局)引入纯类型声明。它不产出任何 JavaScript。

增强已有全局 interface

// env.d.ts
declare global {
  interface ImportMetaEnv {
    readonly VITE_API_URL: string;
  }
}
export {};

import.meta.env.VITE_API_URL;   // ✅ 有类型

在 declare global 里重新打开一个全局 interface,可给现有环境类型(如 Vite 的 ImportMetaEnv)加字段。末尾 export {} 让文件保持 module,增强才正确生效。

装饰器 (6)

旧的实验装饰器(experimental)

// tsconfig: { "experimentalDecorators": true }

function logged<T extends { new(...args: any[]): {} }>(Cls: T) {
  return class extends Cls {
    constructor(...args: any[]) {
      console.log("creating", Cls.name);
      super(...args);
    }
  };
}

@logged
class User {
  constructor(public name: string) {}
}

new User("Lei");   // logs "creating User"

旧装饰器语法需要在 tsconfig 里开 experimentalDecorators: true。Angular / NestJS / TypeORM 仍在大量使用。

Stage 3 标准装饰器(5.0+)

// 不需要 experimentalDecorators
function logged<C extends new (...args: any[]) => any>(
  Cls: C,
  ctx: ClassDecoratorContext,
) {
  return class extends Cls {
    constructor(...args: any[]) {
      console.log("creating", ctx.name);
      super(...args);
    }
  };
}

@logged
class User {
  constructor(public name: string) {}
}

TypeScript 5.0 实现 Stage 3 ECMAScript 装饰器提案,形状与旧版不同,多了 context 对象。新代码该用这个。

方法装饰器

// Stage 3
function timed(
  fn: Function,
  ctx: ClassMethodDecoratorContext,
) {
  return function (this: unknown, ...args: any[]) {
    const t0 = performance.now();
    const result = fn.apply(this, args);
    console.log(`${String(ctx.name)} took ${performance.now() - t0}ms`);
    return result;
  };
}

class Api {
  @timed
  fetch() { /* ... */ }
}

方法装饰器包装或替换方法。context 提供方法名、是否 static、access 等元数据。

访问器装饰器(Stage 3)

function logged<T>(
  target: { get: () => T },
  ctx: ClassGetterDecoratorContext,
) {
  return function (this: unknown) {
    console.log("read", String(ctx.name));
    return target.get.call(this);
  };
}

class Box {
  #v = 1;
  @logged get value() { return this.#v; }
}

getter 装饰器收到 { get } 和一个 ClassGetterDecoratorContext,可返回替换后的访问器。Stage 3 装饰器按目标种类各有不同的 context 类型。

字段装饰器与初始化器

function double(
  _target: undefined,
  ctx: ClassFieldDecoratorContext,
) {
  return function (initial: number) {
    return initial * 2;   // 改写初始值
  };
}

class C {
  @double count = 5;   // 实例化后 count === 10
}

Stage 3 字段装饰器返回一个初始化函数,接收原始值并返回实际存入的值。它对每个实例运行,可变换或校验字段初始值。

addInitializer 自动绑定

function bound(
  fn: Function,
  ctx: ClassMethodDecoratorContext,
) {
  ctx.addInitializer(function (this: any) {
    this[ctx.name] = fn.bind(this);   // 实例化时绑定 this
  });
}

class Btn {
  label = "ok";
  @bound onClick() { return this.label; }
}

Stage 3 的 context 提供 addInitializer,一个在构造期运行的回调。在里面把方法绑到 this,无需写 constructor 就实现了经典的自动绑定。

常见坑 (17)

any vs unknown:最常见的坑

// ❌ any 把检查全关了,错误悄悄溜进运行时
function badParse(input: string): any {
  return JSON.parse(input);
}
const data = badParse("{}");
data.foo.bar.baz();   // 不报错,运行时 TypeError

// ✅ unknown 强制你先收窄
function goodParse(input: string): unknown {
  return JSON.parse(input);
}
const safe = goodParse("{}");
// safe.foo;  ❌ 不让你动
if (typeof safe === "object" && safe !== null && "foo" in safe) {
  // 这里才安全
}

any 把类型检查全关了;unknown 强制收窄。"形状未知的值"(解析 JSON / 第三方回调)一律用 unknown,any 99% 的场景都是错的默认。

非空断言 ! 滥用

// ❌ 滥用 ! 把编译期检查关了
const el = document.getElementById("missing")!;
el.click();   // 运行时 Cannot read 'click' of null

// ✅ 优先显式 null 检查
const el2 = document.getElementById("input");
if (el2) el2.click();

// ✅ 优先可选链
el2?.click();

后缀 ! 只在编译期去掉 null/undefined,运行时不变。断错了就是生产事故。优先 if (x) 或 x?.method()。

type vs interface 选错

// ❌ 库的公共 API 用 type,消费者不能 declare merge 扩展
type LibUser = { id: number; name: string };

// ✅ 库的公共 API 用 interface
interface LibUser2 {
  id: number;
  name: string;
}

// 消费方:
declare module "lib" {
  interface LibUser2 {
    customField?: string;
  }
}

库的公共 API 消费者可能要扩展时用 interface(声明合并能用)。联合、元组、映射、条件类型必须 type 别名。

函数参数协变 / 逆变

// 默认 method 形式参数是 bivariant,不严格
interface Listener {
  onEvent(e: Event): void;
}
const l: Listener = {
  onEvent(e: MouseEvent) {}   // ✅ 默认允许(不安全)
};

// 开 strictFunctionTypes 后 function 形式参数严格逆变
type Listener2 = {
  onEvent: (e: Event) => void;
};
const l2: Listener2 = {
  // onEvent: (e: MouseEvent) => {}   // ❌ 报错
  onEvent: (e: Event) => {}            // ✅
};

方法简写(f(): T)是双变;函数属性(f: () => T)在 strictFunctionTypes 下严格逆变。事件处理类型用函数属性形式更安全。

enum 运行时成本 vs const enum vs 字面量联合

// ❌ enum 会生成运行时代码(双向映射对象)
enum Color { Red, Green, Blue }

// ⚠️ const enum 内联,没运行时代码,但与 isolatedModules / babel 冲突
const enum Status { Ok, Err }

// ✅ 字符串字面量联合:零运行时、JSON 友好、自动补全完美
type ColorLit = "red" | "green" | "blue";

普通 enum 会生成运行时对象(多打包字节)。const enum 内联但与 isolatedModules 冲突。字符串字面量联合是现代推荐,零成本、JSON 友好、自动补全完美。

as Foo 不安全断言把错误藏起来

// ❌ as 是不检查的强转
const x = "hello" as unknown as number;
const y = x.toFixed(2);   // 编译过,运行时 TypeError

// ✅ 用 satisfies 校验
const config = {
  port: 8080,
  host: "localhost",
} satisfies Record<string, string | number>;

// ✅ 真不得已用 as,加注释说明原因
// DOM 保证返回 input,querySelector 不知道
const input = document.querySelector(".x") as HTMLInputElement;

as 绕开编译器检查。校验用 satisfies;as 只在你真的比编译器知道得多时(DOM 查询)才用,并加注释说明原因。

对象 spread 丢失必需 key

interface User { id: number; name: string }

function merge(base: Partial<User>, override: Partial<User>): User {
  // ❌ TS 不知道合并后必填字段都齐了
  // return { ...base, ...override };

  // ✅ 保证必填字段
  return {
    id: override.id ?? base.id ?? 0,
    name: override.name ?? base.name ?? "",
    ...base,
    ...override,
  };
}

两个 Partial<T> spread 出来还是 Partial<T>,编译器无法证明必填字段都在。先给必填 key 显式兜底再 spread。

void 返回类型放宽

// 参数位置的 void 返回,接受任意返回值
type Handler = (e: Event) => void;

const h: Handler = (e) => 42;   // ✅ 合法,返回值被丢弃

// 这是有意为之,让 Array.prototype.forEach 等接受 (x => doSomething())
[1, 2, 3].forEach((x) => x + 1);   // x + 1 返回 number,但 forEach 期望 void

参数位置 void 返回类型接受任意返回值。这是有意设计,让 forEach 等期望 void 的 callback 能接收任意表达式 arrow。知道这个怪癖即可,不一定是 bug。

多余属性检查只针对新鲜字面量

interface Opts { width: number }

// ❌ 直接传字面量触发多余属性检查
// draw({ width: 10, height: 20 });

// ⚠️ 经过变量就绕过了检查(结构兼容)
const o = { width: 10, height: 20 };
draw(o);   // ✅ 不报错

function draw(_: Opts) {}

多余属性检查只在你直接传对象字面量时触发。先赋给变量就退回结构兼容,多出来的属性会悄悄通过。

数组下标访问默认不检查 undefined

const xs = [1, 2, 3];
const x = xs[10];   // 类型是 number,但运行时是 undefined!

// ✅ 开 noUncheckedIndexedAccess 后:
// x 的类型变成 number | undefined

默认情况下 arr[i] 即便越界也标成元素类型,把真实的 undefined 藏起来。开启 noUncheckedIndexedAccess 后,下标访问返回 T | undefined。

空对象类型 {} 过于宽松

// {} 不是"空对象",而是"除 null/undefined 外的任何值"
let a: {} = 42;        // ✅
let b: {} = "x";       // ✅
let c: {} = [1, 2];    // ✅
// let d: {} = null;   ❌

// 想表达"任意非空对象"用 Record<string, unknown> 或 object

类型 {} 表示"除 null 和 undefined 外的任何值",连数字、字符串都收,不只是对象。要真正表达对象用 object 或 Record<string, unknown>。

忘记 await 的 Promise 条件

async function check() {
  // ❌ Promise 永远是 truthy,分支必走
  if (isReady()) { /* 总是进来 */ }

  // ✅ 别忘了 await
  if (await isReady()) { /* 正确 */ }
}
declare function isReady(): Promise<boolean>;

Promise 对象永远是 truthy,所以 if 里没 await 的 promise 永远进分支。开启 typescript-eslint 的 no-misused-promises 能抓到这类 bug。

用 == 比较会触发隐式转换

// == 会做类型转换,结果反直觉
// 0 == ""        // true
// null == undefined  // true
// [] == false    // true

// ✅ 永远用 ===
if (value === 0) { /* ... */ }

宽松的 == 会做隐式转换,得到 0 == "" 这种反直觉的相等。永远用严格的 ===;eqeqeq 这条 lint 规则可在全项目强制。

对象字面量返回类型放宽

// 返回值被推宽成 string,丢了字面量
function makeBad() {
  return { status: "ok" };   // { status: string }
}

// ✅ as const 或显式返回类型保留字面量
function makeGood() {
  return { status: "ok" } as const;   // { readonly status: "ok" }
}

返回的对象字面量会放宽属性类型("ok" 变成 string),破坏下游可辨识联合的收窄。用 as const 或显式返回类型保留字面量。

catch 里的 unknown 需要收窄

try {
  risky();
} catch (e) {
  // e: unknown(useUnknownInCatchVariables 默认开)
  // e.message;   ❌ 不能直接访问
  if (e instanceof Error) {
    console.log(e.message);   // ✅ 收窄后可用
  }
}

4.4 起 catch 变量被标成 unknown,因为可以 throw 任何东西,不止 Error。访问 .message 或 .stack 前先用 instanceof Error 收窄。

可选链可能吞掉真正的错误

// ❌ 过度可选链把本不该缺的字段也容忍了
const name = response?.data?.user?.name ?? "anon";

// 如果 data 一定存在,缺了应该报错而不是默默 "anon"
// ✅ 只在真正可空的环节用 ?.
const name2 = response.data.user?.name ?? "anon";

在本该一定存在的字段上层层 ?.,会把结构性 bug 藏在一个静默兜底后面。只在真正可空的环节用可选链,别当成无脑护身符。

索引签名把拼写错误藏成 any

interface Dict { [k: string]: number }

const d: Dict = { count: 1 };
d.conut;   // ⚠️ 不报错,类型 number,拼错也查不出

// ✅ 已知键用具体 interface,只有真动态才上索引签名

字符串索引签名让任意属性访问都过类型检查,于是 d.conut 这种拼写错误也会作为值类型悄悄通过。索引签名只留给真正动态的 key,已知键显式列出来。

这个工具能做什么

可搜索的 TypeScript 速查表,覆盖日常真在撸的 100+ 段 地道写法,不是凑数的 let x: number = 1 入门列表。十四 大分类:基础(string / number / boolean / null / undefined / never / unknown / any / void、字面量类 型、只读数组、元组),接口 vs 类型别名(什么时候挑哪 个、声明合并、extends vs 交叉、索引签名、调用签名), 联合与交叉(A | B 与 A & B、分配律、共同属性访问、 可辨识联合),泛型(单参 + 多参、extends 约束、默认 参数、泛型 + keyof 的组合约束、泛型函数 vs 泛型类型、 泛型 React 组件),工具类型(Partial、Required、 Readonly、Pick、Omit、Record、Exclude、Extract、 NonNullable、ReturnType、Parameters、Awaited、 ConstructorParameters、InstanceType、ThisParameterType、 OmitThisParameter、Uppercase / Lowercase / Capitalize), 类型收窄(typeof、instanceof、in、相等收窄、真值收 窄、用户自定义类型守卫 is、断言函数 asserts、可辨识 联合 switch 配 never 穷举检查),映射类型(同态映射、 用 as 重命名 key、+ 与 - 修饰 optional / readonly), 条件类型(T extends U ? X : Y、分配律条件类型、裸类 型参数 vs 包裹类型参数、条件类型链),infer 推断 (函数 ReturnType、Promise Awaited、元组头/尾、数组元 素、模板字面量捕获),模板字面量类型(4.1+,与联合 做交叉得到笛卡尔积、递归字符串解析),断言(as Foo、 as const 冻结字面量、satisfies 4.9+ 编译期校验不放宽 类型),声明合并(interface 自动合并、模块增强、全局 声明),装饰器(旧的实验装饰器 vs Stage 3 标准装饰 器、class / method / accessor / field),以及 7 个真 烧时间的坑(any vs unknown 一念之差全失守、非空 ! 滥用线上炸 null、type vs interface 选错命名后期改、 函数参数协变 / 逆变 / 双变看 strictFunctionTypes、 enum 运行时成本 vs const enum vs 字符串字面量联合、 as Foo 不安全断言把错误藏起来、void 返回值放宽接受 任意回调)。每条都带:双语标题、可直接复制的真实代 码、双语说明。搜索框跨标题 / 代码 / 说明三字段一起 过滤,分类胶囊缩范围,一键复制。完全在浏览器里跑, 不连任何服务、不上传。配合 Python / SQL / Vim / Regex 速查覆盖整条技术栈,搭 JSON Formatter 处理 数据。

工具细节

输入
文本
页面会根据工具类型展示文本框、数值控件、文件选择或结构化输入。
输出
即时结果 + 复制
结果区优先给出可操作结果,支持项会显示复制、下载或可视化预览。
隐私
可能使用网络查询
组件源码里检测到网络调用,页面会按工具逻辑处理;敏感内容建议先脱敏。
保存 / 分享
免账号使用
打开页面即可使用;刷新后是否保留结果取决于具体工具。
性能预算
首屏 JS ≤ 32 KB
没有声明 WASM 依赖,适合快速打开和移动端使用。
适用场景
开发运维 · 程序员
分类和职业标签用于推荐相关工具、组织内链,并帮助用户快速判断是否适合当前任务。

怎么用

  1. 1. 输入

    把内容粘贴或拖入工具面板。

  2. 2. 处理

    点击按钮,在浏览器内本地处理,文件不上传。

  3. 3. 复制 / 下载

    一键复制结果或下载到本地。

TypeScript 速查表 适合怎么用

适合穿插在写代码、查问题、做 Review、上线前的小任务里。

适合开发场景

  • 格式化、校验、压缩或检查和代码相关的文本。
  • 把片段整理好再放进文档、工单、提交或交接材料。
  • 不切换工具,快速检查一个小 payload。

开发检查项

  • 压缩、混淆这类不可逆处理,先对副本操作。
  • 除非确认工具本地处理,不要粘贴密钥和敏感片段。
  • 转换后的代码上线前,仍要跑自己的测试或 lint。

下一步可以接着做

这些入口会把当前任务接到更完整的工具链里。

  1. 1 JSON 格式化与校验 浏览器内即时格式化、校验、压缩 JSON,数据不离开本地。 打开
  2. 2 Python 速查表 Python 速查表,100+ 段地道 Python 代码片段,涵盖字符串/列表/字典/文件/异步,带真实例子。 打开
  3. 3 SQL 速查表 SQL 速查表,100+ 条覆盖 SELECT、JOIN、窗口函数、索引,含 MySQL/PostgreSQL/SQLite 方言差异。 打开

真实使用场景

  • 代码评审时一眼定位该用哪个工具类型

    你审一个 400 行的 PR,发现同事手写了一个跟 Partial 一模一样的 mapped type,没用内置的。你筛到「工具 类型」,复制 Pick、Omit、Partial 三段,留三条行内 评论,把 25 行自定义类型换成标准库一行写法。PR 体 积缩小,diff 也重新变得能读。

  • 写一个真能编译过的可辨识联合状态机

    你给一个 fetch hook 建模 idle / loading / success / error 四个状态,老在 error 分支上误访问 .data。筛到 「联合与交叉」,复制可辨识联合加 never 穷举 switch, 编译器立刻拒掉你漏处理的任何分支。12 行的套路一次 性干掉一整类运行时 undefined 报错。

  • 凌晨两点用 infer 抽出 Promise 里的值类型

    同事的接口返回 Promise<User[]>,你只想拿 User 又不 想 import。搜「infer」,复制 Awaited 配 infer 的条件 类型,写 ElementOf<Awaited<ReturnType<typeof fetchUsers>>>。不加新 import、不写 any,接口形状以 后改了类型还能自动跟着变。

  • 用 satisfies 而不是 as 校验配置对象

    你的主题配置有 40 个颜色 key,想让编译器在拼错时报 错,同时保留每个 key 的字面量类型给自动补全用。复制 satisfies 那段,把 `as const` 换成 `satisfies Theme`, 于是拼成 「primry」会直接挂掉构建,而 `theme.primary` 下游照样推断成那个精确的 hex 字符串。

常见踩坑

  • 想表达 unknown 却随手用 any 把报错压下去。any 会把下游所有检查全关掉,unknown 逼你先 typeof 或走守卫再用。复制 unknown 那段做收窄,别图省事。

  • 用 as Foo 来「修」类型错误。强制转换不做校验,只是把不匹配藏到运行时才炸。校验优先用 satisfies,只有当你真比编译器知道得多(比如确定是某个 DOM 元素)才用 as。

  • 联合或推导出来的形状却选了 interface,然后一路跟它较劲。interface 表达不了联合、元组、条件类型。一旦超出平铺对象形状,立刻换成 type 别名。

隐私说明

全部在你浏览器里跑。速查是单个静态页,搜索只对内存里的片段 数组做过滤,你输入的关键词不会离开标签页,也不会写进 URL。 没有代码执行、没有上传、没有任何网络请求。公司代理后面或气 隙机器上都能放心用。

常见问题

类似工具组合

做你这行的人, 还会一起用这些。

Made by Toolora · 100% client-side · Updated 2026-06-13