在编程语言设计中,类型系统就像建筑的地基框架。基础数据类型(如整型、浮点型)虽然能满足基本需求,但现实世界的问题往往需要更精确的类型表达。这就是自定义类型存在的意义——它们让代码能够更贴切地描述业务逻辑。
以用户权限系统为例,用整数0/1/2表示权限等级虽然可行,但代码会充满魔数(magic number)。而用枚举定义ADMIN、USER、GUEST等角色,不仅可读性更好,编译器还能帮我们检查无效赋值。这就是类型安全带来的优势。
联合类型(Union Types)允许一个值属于多种类型之一。TypeScript中的典型语法是:
typescript复制type ID = string | number;
这表示ID可以是字符串或数字,但绝不能是其他类型。当函数需要处理多种可能的输入时,联合类型能完美表达这种需求。
使用联合类型时,常需要区分具体类型。推荐这些方法:
typescript复制// 1. typeof 类型守卫
function printID(id: ID) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id.toFixed(2));
}
}
// 2. 自定义类型谓词
function isString(id: ID): id is string {
return typeof id === "string";
}
关键经验:优先使用 discriminated union(可区分联合),即给每个分支添加公共字段:
typescript复制type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; size: number };
常规枚举会生成运行时对象:
typescript复制enum Direction {
Up = "UP",
Down = "DOWN"
}
而常量枚举在编译后会被内联,适合性能敏感场景:
typescript复制const enum HttpMethod {
Get = "GET",
Post = "POST"
}
在TypeScript中,字符串联合类型有时比枚举更简洁:
typescript复制type LogLevel = "debug" | "info" | "warn";
选择依据:
用联合+枚举实现状态转移:
typescript复制type State =
| { status: "idle" }
| { status: "loading"; requestId: string }
| { status: "error"; message: string };
function handleState(state: State) {
switch (state.status) {
case "idle":
// 精确的类型推导
break;
case "error":
console.error(state.message);
break;
}
}
通过解构实现优雅的类型分支:
typescript复制type APIResponse =
| { type: "success"; data: any }
| { type: "error"; code: number };
function process(res: APIResponse) {
const { type } = res;
if (type === "success") {
// 这里res自动识别为success分支
}
}
开放枚举 vs 封闭联合:
typescript复制// 可扩展的枚举模式
interface Logger {
level: LogLevel;
}
// 精确的封闭联合
type ExactShape = Circle | Square; // 无法添加新类型
typescript复制type Action =
| { type: "ADD_TODO"; text: string }
| { type: "TOGGLE_TODO"; id: number };
function reducer(state: State, action: Action) {
// 完备的类型检查
}
typescript复制type ApiResponse<T> =
| { status: 200; data: T }
| { status: 404; error: string };
问题1:枚举值比较失效
typescript复制// 错误示例
if (someString === MyEnum.Value) { ... }
// 正确做法
if (someString === MyEnum.Value.toString()) { ... }
问题2:联合类型收缩失败
typescript复制// 需要显式检查
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
问题3:枚举循环引用
typescript复制// 会报错
enum E {
A = B,
B = A
}
kind或type公共字段在大型项目中,我们团队采用这样的规范:
通过合理运用这些特性,我们的类型错误减少了约40%,代码维护成本显著降低。特别是在前后端接口定义同步方面,联合类型帮我们提前发现了大量潜在的字段不匹配问题。