这本字典怎么用
按用途分七组:类型标注、接口与别名、泛型、工具类型、类型收窄、模块与配置、报错速查。每条四件事:签名、一句话语义(写”什么时候用、和谁的区别”)、最小示例、常见坑。字典是查的不是读的——先搜文末速查表定位,再跳回条目。
概念背景见TypeScript 语法要点;条目里的运行环境为 Node.js + tsc,报错行为以 strict 模式为准。
一、类型标注
组内共性:标注是契约不是注释,多数位置靠推断;标注的主战场是函数参数与导出 API。
let x: T = v(变量标注)
显式声明变量类型,推断会出错或想放宽时才用。
let ids: string[] = [];
let pair: [string, number] = ["a", 1]; // 元组:长度与各位类型都固定
坑:元组是定长的,push 之后长度信息与类型脱节,运行时可能比类型认的更长。
function f(a: T): R(函数签名标注)
参数没有推断来源必须标;导出函数的返回值建议标,把承诺写死在定义处。
export function parseIds(raw: string): number[] {
return raw.split(",").map(Number);
}
坑:可选参数 flag?: boolean 实际是 boolean | undefined,与默认值 flag = false 的行为不同——后者不必传且不产生 undefined。
字面量类型与 as const
把值本身当类型,适合枚举式取值;as const 让对象与数组的一切推断落到字面量。
let status: "open" | "closed" = "open";
const dirs = ["up", "down"] as const; // readonly ["up","down"]
坑:let x = "hello" 推断为 string(可变变量放宽),只有 const 或 as const 才保住字面量。
readonly 与 ?
readonly 编译期禁改(运行时无效);? 标记可能缺失。
interface Point { readonly x: number; y?: number }
const p: Point = { x: 1 };
// p.x = 2 // 报错
坑:readonly 不递归——readonly T[] 禁止改数组,元素内部照改不误。
二、接口与别名
组内共性:都能描述对象形状;type 能做类型运算,interface 能被重复声明合并与 extends。
interface
描述对象/函数形状,可被 extends 扩展、可同名合并(declaration merging)。
interface Todo { id: string; done: boolean }
interface Todo { text: string } // 与上合并,Todo 现在有三个字段
坑:同名静默合并是库扩展机制,业务代码里两个文件同名 interface 会把字段悄悄混进来,排查困难。
type
给任意类型起名:联合、元组、映射、条件类型的唯一载体。
type ID = string | number;
type Handler = (e: string) => void;
坑:type 无合并特性,重名直接报错——这反而是优点。
extends 与交叉 &
interface 用 extends 继承,type 用 & 组合;都表达”且”。
interface Base { id: string }
interface Task extends Base { done: boolean }
type Task2 = Base & { done: boolean };
坑:交叉时同名字段类型冲突会得到 never,见语法要点篇的例子。
索引签名 [k: string]: T
描述”任意键都映射到 T”,动态键容器用。
interface Counts { [key: string]: number }
const c: Counts = { ok: 1, fail: 0 };
坑:索引签名放行任意键,拼错键名不报错——确定键集合时用 Record 更严。
三、泛型
组内共性:把类型做成参数,保住输入输出之间的类型关联;先有第二形态再抽象。
泛型函数
让调用方决定类型,函数体保住关联。
function firstOr<T>(list: T[], fallback: T): T {
return list.length ? list[0] : fallback;
}
坑:函数体用不到类型关联时不要写泛型——那是装饰,不是抽象。
T extends U(约束)
限制类型参数的范围,换取在函数体内使用 U 成员的资格。
function len<T extends { length: number }>(x: T): number {
return x.length; // 无约束这里就报错
}
坑:约束过宽等于没约束,过窄调用方处处断言;从”函数体真正用了什么”倒推。
默认类型参数 T = D
调用方不传时的兜底类型。
function fetchJson<T = unknown>(url: string): Promise<T>;
// 不显式传 T 时拿到 unknown,逼调用方校验
坑:默认 any 是坏默认——校验压力被转移给每一个调用点。
四、工具类型与类型运算
组内共性:类型层面的函数,输入类型输出类型;组合使用时从内往外读。
keyof T
取对象类型的所有键组成联合。
interface Todo { id: string; done: boolean }
type TodoKey = keyof Todo; // "id" | "done"
坑:keyof 拿的是类型声明的键,运行时对象多出来的键不在其中。
typeof x(类型查询)
从已有值反取类型,配置对象与常量表常用。
const defaults = { host: "127.0.0.1", port: 8080 };
type Defaults = typeof defaults; // { host: string; port: number }
坑:与值层面的 typeof 运算符同名不同物——一个在类型位置,一个在表达式位置。
Partial / Required
全部属性变可选 / 变必填;更新接口入参的标准姿势。
function update(id: string, patch: Partial<Todo>) { /* ... */ }
update("t1", { done: true }); // 只传要改的
坑:Partial 连 id 也变可选,更新入参想排除某些字段用 Pick 组合。
Pick / Omit
挑字段 / 删字段,产出视图类型。
type TodoBrief = Pick<Todo, "id">;
type TodoDraft = Omit<Todo, "id">; // 创建入参:id 由服务端生成
坑:K 写了 T 里不存在的键不总是报错(联合宽化场景),键名靠字面量保真。
Record
键集合 K 到 V 的映射,比索引签名严格。
type Env = "dev" | "prod";
const urls: Record<Env, string> = { dev: ":8080", prod: ":443" };
// 少写一个环境的键,编译期就报错
坑:K 是 string 时退化为索引签名,失去键检查。
ReturnType / Awaited
取函数返回类型 / 解开 Promise 拿到最终值类型。
declare function loadTodo(): Promise<Todo>;
type One = Awaited<ReturnType<typeof loadTodo>>; // Todo
坑:异步函数必须 Awaited 包 ReturnType,否则拿到的是 Promise 包装。
五、类型收窄与断言
组内共性:联合类型靠收窄变成可用类型;断言是收窄的替代品,每次使用都该有理由。
typeof 收窄
原始类型分支的默认触发器。
function size(x: string | string[]): number {
return typeof x === "string" ? x.length : x.join("").length;
}
坑:typeof null === “object”,判空用 x === null。
instanceof / in
类实例判别 / 键存在性判别。
if (err instanceof TypeError) { /* ... */ }
if ("code" in err) { console.log(err.code); }
坑:跨 realm(iframe、vm 模块)实例的原型链不同,instanceof 会失手。
判别联合 + switch
互斥状态的标准建模:字面量判别字段 + 穷尽 switch。
type State =
| { kind: "idle" }
| { kind: "ok"; data: string }
| { kind: "err"; msg: string };
function view(s: State) {
switch (s.kind) {
case "idle": return "待机";
case "ok": return s.data; // 收窄成功
default: return s.msg;
}
}
坑:判别字段必须是字面量类型;写成 string 就失去判别力。
类型谓词 x is T
自定义校验函数的返回类型,让 if 之后自动收窄。
function isTodo(x: unknown): x is Todo {
return typeof x === "object" && x !== null && "id" in x;
}
const raw: unknown = JSON.parse(text);
if (isTodo(raw)) view({ kind: "ok", data: raw.id });
坑:谓词只做类型层面的信任,函数体写错(恒真)编译器不查——校验逻辑要自己负责。
非空断言 !
告诉编译器”这里不会是 null/undefined”,绕过检查。
const el = document.getElementById("app")!;
坑:运行时真没有就是 TypeError,把检查义务转移给了未来的自己。
as 断言 / satisfies
as 强制改类型(骗编译器);satisfies 验形状且保推断(TS 4.9+)。用 as 覆盖校验逻辑是字典里最典型的反模式:编译器闭嘴不等于类型对了,脏数据从此带毒运行。
const cfg = { retries: 3 } satisfies Record<string, number>;
const n = "42" as unknown as number; // 双重断言:几乎必错
坑:as 不做任何转换,只是闭嘴;能 satisfies 或校验解决的不要 as。
六、模块与配置(tsconfig 关键项)
组内共性:tsconfig 决定编译器严格程度与模块解析方式;从 strict 起步,按需加码。
strict: true
严格系开关的总闸(strictNullChecks、noImplicitAny 等一并生效)。新项目无脑开,存量项目开它是 ROI 最高的一次性投入。
{ "compilerOptions": { "strict": true } }
坑:不开 strictNullChecks 时 undefined 不在类型里,本文一半的收窄条目都失去意义。
noUncheckedIndexedAccess
索引访问返回 T | undefined,承认”数组可能越界、键可能不存在”。
const xs = [1, 2];
const a = xs[0]; // number | undefined,用前必须收窄
坑:开了它之后旧代码大量报错,建议新项目直接开、老项目随迭代推进。
module / moduleResolution
module 定输出模块格式(esnext / nodenext / node20 等),moduleResolution 定找模块的方式,两者要配套(nodenext 配 nodenext,bundler 配打包器场景)。
{ "module": "nodenext", "moduleResolution": "nodenext" }
坑:Node 直接跑(不经打包)却配 bundler,运行时扩展名解析行为对不上。
verbatimModuleSyntax 与 import type
强制类型导入显式化:import type 的内容编译后被整体擦除,不产生运行时导入。
import type { Todo } from "./todo.js"; // 纯类型,擦除
import { loadTodo } from "./todo.js"; // 有运行时效果
坑:编译产物是 JS,相对导入要写 .js 扩展名(tsc 按 JS 输出解析),漏写运行时找不到模块。
isolatedModules
假设每个文件会被单文件转译器(esbuild、swc)单独处理,禁止跨文件的类型再导出等隐式依赖。多数构建链与转译运行工具都要求它,建议常开。
坑:export { SomeType } from ... 这种不标 type 的重导出直接报错——这正是它要拦的。
paths(路径别名)
编译期把 @/x 映射到 src/x,消除 ../../。
{ "baseUrl": ".", "paths": { "@/*": ["src/*"] } }
坑:paths 只影响 tsc 的类型检查与认 tsconfig 的打包器,Node 直接运行编译产物不认,需要运行时侧对应配置。
七、常见报错速查
按”看到这条先检查什么”组织,全部以 strict 模式为准:
| 报错码 | 典型信息 | 先检查什么 |
|---|---|---|
| TS2322 / TS2345 | Type ‘x’ is not assignable to type ‘y’ | 两边形状差在哪:缺字段、多了字段(多余属性检查只在字面量上触发)、字面量被放宽 |
| TS2339 | Property ‘p’ does not exist on type ‘T’ | 用之前收窄了吗:联合未收窄时只能碰公共成员 |
| TS2532 / TS18048 | possibly ‘undefined’ | 加空值分支、默认值 ?? 或 if 收窄;非空断言 ! 是最后手段 |
| TS7006 | Parameter implicitly has an ‘any’ type | 没开 strict 或漏标参数;补标注而不是关检查 |
| TS2352 | Conversion of type may be a mistake | as 两边类型无重叠,多半该用类型谓词校验而不是断言 |
| TS2304 / TS2307 | Cannot find name / module | 相对导入扩展名(.js)、paths 是否运行时生效、package.json 的 type 字段 |
| TS2769 | No overload matches this call | 看最后一个重载的报错细节,多半是参数顺序或形状差一点 |
| TS2564 | Property has no initializer | strict 下类字段必须初始化或标 !: 断言赋值 |
全量速查表
| 条目 | 一句话用途 | 坑 |
|---|---|---|
| let x: T | 显式定类型 | 推断已有就别标 |
| 函数标注 | 参数必标、导出返回值建议标 | ? 与默认值不等价 |
| 字面量/as const | 值当类型用 | let 推断会放宽 |
| readonly / ? | 禁改 / 可缺失 | readonly 不递归 |
| interface | 可扩展的形状 | 同名静默合并 |
| type | 类型运算载体 | 重名报错(是优点) |
| extends / & | 继承与组合 | 冲突字段交叉成 never |
| 索引签名 | 任意键容器 | 拼错键不报错 |
| 泛型 | 保类型关联 | 没关联别用泛型 |
| extends 约束 | 给类型参数立法 | 宽窄要贴函数体 |
| 默认 T = D | 兜底类型 | 默认 any 是坏默认 |
| keyof | 取键联合 | 不含运行时多余的键 |
| typeof(类型) | 从值取类型 | 与值层 typeof 同名 |
| Partial / Required | 全可选 / 全必填 | 连 id 也可选 |
| Pick / Omit | 挑 / 删字段 | 键名要字面量保真 |
| Record | 键集到值的映射 | K 写 string 就退化 |
| ReturnType / Awaited | 取返回 / 解 Promise | 异步记得套 Awaited |
| typeof 收窄 | 原始类型分支 | typeof null 是 object |
| instanceof / in | 实例 / 键判别 | 跨 realm 失手 |
| 判别联合 | 互斥状态建模 | 判别字段须字面量 |
| x is T | 自定义校验收窄 | 函数体写错没人查 |
| 非空 ! | 绕空值检查 | 运行时真空就炸 |
| as / satisfies | 改类型 / 验形状 | as 只是闭嘴不转换 |
| strict | 严格总闸 | 不开则收窄全失效 |
| noUncheckedIndexedAccess | 索引访问认账越界 | 旧代码报错量大 |
| module 组合 | 模块格式与解析 | 运行方式要配套 |
| verbatimModuleSyntax | 类型导入显式化 | 导入要写 .js 后缀 |
| isolatedModules | 单文件转译安全 | 拦隐式类型重导出 |
| paths | 路径别名 | 运行时不认 |
| 报错速查 | 见第七节表 | 以 strict 为准 |
延伸阅读
- 概念为什么这样设计,看TypeScript 语法要点;字典负责查,那篇负责懂。
- 全部条目的一次综合运用:用 TypeScript 写一个命令行待办工具。
- 宿主平台的 API 字典:Node.js 基础知识参考字典。
版本说明:截至写作(2025-02-22),npm latest 为 TypeScript 5.7.x(2024-11 发布);本文条目语法跨 5.x 适用(satisfies 需 4.9+,verbatimModuleSyntax 需 5.0+)。tsconfig 具体项行为以官方文档为准。