本附录目标:把全书用到的 TypeScript 语法压缩成十几张「写法 / 说明 / 常见坑」对照表。写代码时不必回忆章节号,直接翻到这里;遇到报错时,先在下表定位写法,再回正文看完整推导。
附录 A TypeScript 语法速查表
本附录不是教程,而是查阅手册。正文各章负责「为什么这样设计」,本附录只负责「怎么写、容易错在哪」。建议第一次通读一遍建立索引感,之后把它当成案头卡片。
表中的类型写法默认运行在 strict: true 之下;若你的项目未开启严格模式,部分「常见坑」不会立即报错,但问题依然存在。
原始类型与字面量类型
| 写法 | 说明 | 常见坑 |
|---|
let n: number = 1 | 数字,含整数、浮点、NaN、Infinity | 没有 int / float 之分,金额运算注意精度 |
let s: string = "a" | 字符串,单双引号与反引号等价 | 反引号会做插值,勿与普通串混写 |
let b: boolean = true | 布尔 | 注解别写 Boolean(那是包装对象类型) |
let big: bigint = 1n | 大整数,字面量带 n 后缀 | 与 number 不能直接混合运算 |
let sym: symbol = Symbol() | 唯一符号 | 需要 target 不低于 ES2015 |
let u: undefined | 未定义 | 严格模式下 undefined 只等于自身 |
let nul: null | 空值 | 严格空值检查下不兼容其他类型 |
let v: void | 函数无返回值 | 变量类型写 void 几乎没有意义 |
let a: any | 关闭检查 | 会沿赋值链扩散,污染下游所有类型 |
let un: unknown | 安全的顶层类型 | 使用前必须先收窄 |
let nv: never | 永不发生的类型 | 只应出现在抛错函数或穷尽分支 |
let o: object | 非原始值 | 不含 null,也不等于空对象类型 {} |
type T = "a" | "b" | 字符串字面量联合 | 忘了 as const 时会被推断成宽泛的 string |
type N = 1 | 2 | 3 | 数字字面量联合 | 浮点字面量同样能作为类型 |
type B = true | false | 布尔字面量 | 等价于 boolean 的联合展开 |
类型注解的书写位置
| 位置 | 写法 | 说明 |
|---|
| 变量 | const x: number = 1 | 变量名后加冒号 |
| 函数参数 | function f(a: string) {} | 参数名后加冒号 |
| 返回值 | function f(): number {} | 参数括号后加冒号 |
| 箭头函数 | const f = (a: string): number => 1 | 同上,返回类型写在箭头前 |
| 对象属性 | { id: number } | 属性名后加冒号 |
| 对象解构 | const { id }: { id: number } = o | 解构模式后加冒号 |
| 数组解构 | const [a]: [number] = arr | 同上,按位置对应 |
| 类字段 | class C { n: number = 0 } | 字段名后加冒号 |
| 类型断言 | x as number | 表达式后接 as |
| 非空断言 | x! | 表达式后接叹号 |
| 满足式 | x satisfies T | 表达式后接 satisfies |
| 类型实参 | f<number>(1) | 调用时用尖括号 |
数组、元组与枚举
| 写法 | 说明 | 常见坑 |
|---|
number[] | 数组简写 | 与 Array<number> 完全等价 |
Array<number> | 泛型数组 | 只读场景应改用 ReadonlyArray<number> |
readonly number[] | 只读数组 | 不能 push,但元素对象本身仍可变 |
[string, number] | 元组 | 长度固定,越界访问会报错 |
[string, number?] | 可选元组元素 | 可选元素只能放在末尾 |
[string, ...number[]] | 带剩余元素的元组 | 剩余元素同样只能放末尾 |
readonly [string, number] | 只读元组 | 常作为 as const 的推断结果出现 |
enum Color { Red, Green } | 数字枚举 | 默认从 0 开始,且运行时存在反向映射 |
enum Color { Red = "red" } | 字符串枚举 | 无反向映射,对外契约优先选它 |
const enum Dir { Up } | 常量枚举 | 与 isolatedModules 冲突,跨包慎用 |
as const | 字面量冻结 | 对象变只读,数组变只读元组 |
as const 的效果值得单独对照:
const a = ["x", "y"];
// string[]
const b = ["x", "y"] as const;
// readonly ["x", "y"]
const c = { kind: "user" };
// { kind: string }
const d = { kind: "user" } as const;
// { readonly kind: "user" }
函数签名
| 写法 | 说明 | 常见坑 |
|---|
function f(a: string): void | 普通函数 | 返回类型可省略,但公开 API 建议显式写出 |
(a: string) => void | 函数类型 | 参数名可省略,只留类型 |
function f(a?: string) | 可选参数 | 可选参数必须排在必填参数之后 |
function f(a = 1) | 默认值 | 有默认值即隐式为可选,类型可自动推断 |
function f(...rest: number[]) | 剩余参数 | 只能是最后一个参数 |
function f(this: Window) | this 参数 | 只在类型层存在,不产生真实形参 |
| 重载签名 + 实现签名 | 重载 | 实现签名对外不可见,且必须写在最后 |
function f(a: string | number) | 联合参数 | 实现签名必须兼容全部重载签名 |
(...args: any[]) => any | 任意函数 | 赋给 Function 会丢失全部参数类型 |
f?: () => void | 可选回调 | 调用前需判空,或用 f?.() |
type F = { (a: string): void; tag: string } | 可调用对象 | 用 interface 写可读性更好 |
重载与实现签名的关系:
function parse(v: string): number;
function parse(v: number): string;
function parse(v: string | number): string | number {
return typeof v === "string" ? Number(v) : String(v);
}
const n = parse("1"); // number
const s = parse(1); // string
接口与类型别名
| 写法 | 说明 | 常见坑 |
|---|
interface User { id: number } | 定义对象结构 | 同名 interface 会自动声明合并 |
type User = { id: number } | 类型别名 | 同名 type 重复声明会直接报错 |
{ id?: number } | 可选属性 | 与「值为 undefined」不等价,见 exactOptionalPropertyTypes |
{ readonly id: number } | 只读属性 | 只防重新赋值,不防内层可变 |
{ [k: string]: number } | 索引签名 | 会要求所有具名属性都兼容该签名 |
{ [k: string]: number; id: number } | 具名 + 索引 | 具名属性类型必须是索引类型的子类型 |
interface A extends B {} | 接口继承 | 可多继承,冲突成员类型不兼容会报错 |
type A = B & C | 交叉类型 | 同名属性类型冲突时会被推成 never |
interface A { m(): void } | 方法简写 | 与方法属性写法在严格函数类型下有细微差别 |
type Union = A | B | 联合类型 | 访问成员前必须先收窄 |
type Fn = (a: string) => void | 函数类型别名 | interface 也能表达,但简写更常见 |
类
| 写法 | 说明 | 常见坑 |
|---|
class C {} | 类 | 类同时拥有「实例类型」与「构造器类型」 |
public / protected / private | 访问修饰符 | private 只在类型层,运行时仍可访问 |
#field | 真私有字段 | 运行时真私有,需要 target 不低于 ES2022 |
readonly id: number | 只读字段 | 只能在声明处或构造器内赋值 |
constructor(private name: string) {} | 参数属性 | 自动生成同名字段,可与修饰符、readonly 组合 |
static count = 0 | 静态成员 | 静态成员属于构造器类型,不属于实例 |
abstract class A {} | 抽象类 | 不能 new,抽象方法必须在子类实现 |
class C implements I {} | 接口实现 | 只做形状检查,不产生任何继承关系 |
class C extends B {} | 继承 | 子类构造器必须先 super() 再用 this |
override m() {} | 显式覆写 | 需要 noImplicitOverride 配合才强制 |
get x() {} / set x(v) {} | 访问器 | 较新版本可分别声明读写类型,旧版必须一致 |
declare field: T | 仅声明不产出 | 与 useDefineForClassFields 的语义相关 |
泛型
| 写法 | 说明 | 常见坑 |
|---|
<T>(x: T) => T | 泛型函数 | 参数名随意,约定用 T / U / K / V |
<T extends object> | 泛型约束 | 不加约束时不能访问任何成员 |
<T extends keyof U> | 键约束 | 常与索引访问 U[T] 配合 |
<T = string> | 默认类型参数 | 只有无法推断时才会生效 |
<const T> | const 类型参数 | 让实参按字面量推断,免写 as const |
<T,>(x: T) => x | 箭头函数中的泛型 | .tsx 文件里必须加逗号,否则被当成 JSX |
class Box<T> {} | 泛型类 | 静态成员不能引用类的类型参数 |
interface Repo<T> {} | 泛型接口 | 方法级还能再引入独立参数 |
Map<K, V> | 多参数泛型 | 参数顺序即语义,不要随意调换 |
T extends U ? X : Y | 泛型上的条件 | 会分发联合类型,用 [T] extends [U] 关闭分发 |
高级类型
| 写法 | 说明 | 常见坑 |
|---|
keyof T | 键的联合 | 对联合类型取 keyof 得到的是公共键 |
typeof x | 取变量的类型 | 只对值有效,不能作用于类型别名 |
T[K] | 索引访问类型 | K 必须是 keyof T 的子集 |
{ [K in keyof T]: T[K] } | 映射类型 | 默认保留修饰符,用 -? / -readonly 移除 |
{ [K in keyof T as NewKey]: T[K] } | 键重映射 | 重映射成 never 即可删除该键 |
T extends U ? X : Y | 条件类型 | 裸 T 会分发联合,[T] 包裹可关闭 |
T extends (infer R)[] ? R : never | infer 推断 | 同一条件里同名 infer 会互相约束 |
infer R extends string | 受限 infer | 旧版本不支持,需改用嵌套条件 |
`get${Capitalize<K>}` | 模板字面量类型 | 只能作用于字符串、数字、布尔字面量 |
Uppercase<S> | 字符串工具类型 | 仅内置四个,其他大小写规则需自写 |
T & { __brand: "x" } | 品牌类型 | 运行时不存在,仅在类型层区分 |
Awaited<T> | 递归解开 Promise | 手写 infer 版本容易漏掉多层嵌套 |
模块与声明
| 写法 | 说明 | 常见坑 |
|---|
import { a } from "./m" | 具名导入 | 路径解析受 moduleResolution 影响 |
import type { T } from "./m" | 仅类型导入 | 编译后整句消失,不产生运行时副作用 |
export type { T } | 仅类型导出 | 与 isolatedModules 配合时必需 |
export default f | 默认导出 | 与 CJS 互操作时命名容易错位 |
export = f | CJS 风格导出 | 不能与 ESM 语法混用 |
import x = require("m") | CJS 风格导入 | 仅 CJS 输出目标可用 |
declare const VERSION: string | 声明值 | 只描述已存在的运行时值,不产出代码 |
declare module "m" {} | 声明模块 | 为无类型库打补丁的标准做法 |
declare global {} | 全局增强 | 所在文件必须是模块(含 import 或 export) |
declare namespace N {} | 命名空间 | 新代码优先用模块,命名空间主要用于声明 |
/// <reference types="node" /> | 三斜线指令 | 现代项目多用 tsconfig 的 types 字段替代 |
类型断言与收窄
| 写法 | 说明 | 常见坑 |
|---|
x as T | 类型断言 | 只在兼容方向允许,跨类型需先 as unknown |
x! | 非空断言 | 断言错了运行时照样崩,慎用 |
x satisfies T | 满足式 | 检查兼容性但不改变推断结果 |
typeof x === "string" | typeof 守卫 | 只对原始类型精确 |
x instanceof Date | instanceof 守卫 | 跨 iframe 或多 realm 场景会失效 |
"a" in x | in 守卫 | 只收窄到含该键的成员 |
Array.isArray(x) | 内置守卫 | 对只读数组需额外处理 |
x is T | 自定义类型守卫 | 返回布尔,但正确性由你保证 |
asserts x is T | 断言函数 | 函数返回 void,调用后立即收窄 |
asserts x | 断言真值 | 与上者区别:只断言非空,不改变类型 |
switch + never 兜底 | 穷尽检查 | 需要开启严格空值检查才有效 |
const _x: never = value | 穷尽兜底 | 漏掉分支时恰好在此处报错 |
类型推断与显式标注的取舍
「该不该写类型」是新手最常纠结的问题。判断标准只有一条:这行代码的类型是否可能与你预期不同。
| 场景 | 建议 | 理由 |
|---|
const n = 1 | 不写 | 推断为字面量 1,比手写更精确 |
let n = 1 | 不写 | 推断为 number,符合预期 |
const obj = { a: 1 } | 不写 | 结构清晰,推断即所得 |
| 函数返回值(内部) | 不写 | 让实现即契约,减少重复 |
| 函数返回值(导出) | 必须写 | 公开 API 的契约必须显式 |
空数组 [] | 必须写 | 否则推断为空联合,push 报 never |
空对象 {} | 必须写 | 否则无法索引任何属性 |
| 回调参数 | 不写 | 由被调用方上下文推断 |
| 泛型函数调用 | 尽量不写 | 写死类型实参会丢掉推断的精确性 |
| 第三方数据边界 | 必须写 | 类型是「声称」,需运行时校验兜底 |
一个反例说明「不写反而更好」:
// 写死了宽类型,丢掉了字面量信息
const config: Record<string, string> = { mode: "dark" };
// config.mode 的类型是 string,不是 "dark"
// 让编译器推断,保留字面量
const config2 = { mode: "dark" } as const;
// config2.mode 的类型是 "dark"
常见坑速查
| 症状 | 原因 | 解决 |
|---|
字面量被推断成 string | 缺少 as const 或 const 类型参数 | 加 as const,或用 <const T> |
可选属性传 undefined 报错 | 开启了 exactOptionalPropertyTypes | 显式写 id?: number | undefined |
Object is possibly 'undefined' | 严格空值检查生效 | 判空、可选链 ?.、或默认值 |
| 联合类型上调方法报错 | 方法不在所有成员上 | 先用守卫收窄,或用判别联合 |
| 重载调用总是匹配不到 | 实现签名与重载签名不兼容 | 让实现签名参数放宽为各重载的并集 |
交叉类型属性变成 never | 两类型同名属性不兼容 | 改用 Omit 去掉冲突字段再交叉 |
| 索引签名报「属性不兼容」 | 具名属性类型不是索引类型的子类型 | 让索引类型覆盖全部具名属性类型 |
as 之后运行时报错 | 断言只是让编译器闭嘴 | 用类型守卫或运行时校验替代 |
| 枚举值意外相等 | 数字枚举成员自动递增 | 对外契约改用字符串枚举 |
小结
- 本附录按「类型 → 注解位置 → 数组元组 → 函数 → 接口 → 类 → 泛型 → 高级类型 → 模块 → 收窄」的顺序组织,每一节都是一张可直接查的表。
- 表中的「常见坑」列才是真正值钱的部分:绝大多数 TypeScript 报错不是语法不会写,而是推断结果与预期不同。
- 类型注解的书写位置只有「冒号、
as、satisfies、尖括号」四种形态,记熟这四种就不会找不到该在哪里写类型。 - 高级类型全部建立在
keyof、索引访问、映射类型、条件类型四件事之上,遇到复杂类型先拆成这四步。 - 遇到报错时,先在「常见坑速查」表里定位症状,再回到对应正文小节看完整推导。
- 附录 B 会在这份语法表之上,继续整理标准库工具类型与常用类型模式。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。