上篇我们完成了 TypeScript 基础:类型注解、接口、泛型、基本工具类型。
但“会用”和“精通”之间,横亘着类型编程的深水区。
这篇将深入生产级 TypeScript 的核心实战——没有基础语法,不注水,全是硬核干货。
一、类型守卫与自定义守卫:让类型系统真正“懂你”
1.1 内置类型守卫
TypeScript 内置了四种类型守卫:
typeof:区分string、number、boolean、symbol、bigint、function、objectinstanceof:区分 ES class 实例in:检查对象上是否存在某个属性可辨识联合 (Discriminated Union):利用共同的
type字段
1.2 自定义类型守卫(is关键字)
写一个真正能收窄类型的函数:
typescript
interface Cat { meow: () => void; name: string; } interface Dog { bark: () => void; age: number; } // 自定义守卫:返回类型是 pet is Cat function isCat(pet: Cat | Dog): pet is Cat { return (pet as Cat).meow !== undefined; } // 使用 function play(pet: Cat | Dog) { if (isCat(pet)) { pet.meow(); // ✅ pet 被收窄为 Cat console.log(pet.name); } else { pet.bark(); // ✅ pet 被收窄为 Dog console.log(pet.age); } }与普通
boolean的区别:pet is Cat告诉 TypeScript 编译器“当函数返回 true 时,传入的参数就是 Cat 类型”,从而在if分支内自动收窄类型。
1.3 守卫的高级模式:asserts断言
用于断言某个条件成立,否则抛出错误。之后变量类型会被收窄:
typescript
function assertIsString(value: unknown): asserts value is string { if (typeof value !== 'string') { throw new Error('Not a string'); } } let input: unknown = 'hello'; assertIsString(input); input.toUpperCase(); // ✅ 此时 input 被收窄为 string二、高级类型编程:类型体操的核心心法
2.1 条件类型(Conditional Types)
T extends U ? X : Y—— 根据类型关系动态选择类型。
typescript
type IsArray<T> = T extends any[] ? true : false; type A = IsArray<string[]>; // true type B = IsArray<number>; // false // 结合 infer(类型推断) type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; type Fn = (x: number) => string; type R = ReturnType<Fn>; // string
2.2 映射类型(Mapped Types)
遍历联合类型生成新类型:
typescript
type Readonly<T> = { readonly [P in keyof T]: T[P]; }; type Partial<T> = { [P in keyof T]?: T[P]; }; // 更高级的:添加或删除修饰符 type Mutable<T> = { -readonly [P in keyof T]: T[P]; // 移除 readonly }; type Required<T> = { [P in keyof T]-?: T[P]; // 移除可选 ? };2.3 模板字面量类型(Template Literal Types)
TypeScript 4.1+ 提供的字符串类型编程能力:
typescript
type EventName = `on${Capitalize<string>}`; type ClickEvent = EventName; // `on${Capitalize<string>}`,实际使用时需配合泛型 // 实际例子:路由参数解析 type RouteParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? Param | RouteParams<Rest> : T extends `${infer _Start}:${infer Param}` ? Param : never; type Params = RouteParams<'/user/:id/post/:pid'>; // 'id' | 'pid'2.4 递归类型(Recursive Types)
构建树形结构或深度操作:
typescript
type JsonValue = | string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }; // 深度只读 type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]; };三、生产级工具类型精讲
除了内置的Partial、Required、Pick、Omit、Record、Exclude、Extract、NonNullable、ReturnType、Parameters外,这些也是必备的:
3.1Awaited<T>(TypeScript 4.5+)
解开 Promise 的嵌套:
typescript
type Result = Awaited<Promise<Promise<number>>>; // number
3.2NoInfer<T>(TypeScript 5.4+)
阻止泛型推断从使用侧反向传播,用于函数参数中强制指定泛型:
typescript
declare function create<T>(value: T, defaultValue: NoInfer<T>): T; // 调用时 defaultValue 的类型必须精确匹配 T,不会被推断扩展
3.3 自定义高频工具类型
DeepPartial<T>—— 递归可选:
typescript
type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;ValueOf<T>—— 获取对象所有值的联合类型:
typescript
type ValueOf<T> = T[keyof T];
OmitNever<T>—— 过滤掉值为never的字段:
typescript
type OmitNever<T> = { [K in keyof T as T[K] extends never ? never : K]: T[K] };UnionToIntersection<U>—— 联合类型转交叉类型(高级技巧):
typescript
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; // 例子: UnionToIntersection<{ a: 1 } | { b: 2 }> => { a: 1 } & { b: 2 }四、TSConfig 生产级配置
4.1 核心编译选项(tsconfig.json)
生产项目建议开启以下严格模式全家桶:
json
{ "compilerOptions": { // 严格模式(建议全部 true) "strict": true, // 总开关,开启下面所有 "noImplicitAny": true, // 禁止隐式 any "strictNullChecks": true, // null/undefined 严格检查 "strictFunctionTypes": true, // 函数类型双向协变检查 "strictBindCallApply": true, // bind/call/apply 严格检查 "strictPropertyInitialization": true,// 类属性必须初始化 "noImplicitThis": true, // this 隐式 any 报错 "alwaysStrict": true, // 每个文件生成 "use strict" // 额外安全选项 "noUnusedLocals": true, // 禁止未使用的局部变量 "noUnusedParameters": true, // 禁止未使用的参数 "noImplicitReturns": true, // 函数所有分支必须显式返回 "noFallthroughCasesInSwitch": true, // switch 禁止落空 // 模块与输出 "module": "NodeNext", // 或 "ESNext" 取决于目标环境 "moduleResolution": "NodeNext", "target": "ES2022", // 现代 Node.js 可用 ES2022+ "lib": ["ES2022"], "outDir": "./dist", "rootDir": "./src", "removeComments": true, // 生产去除注释 "sourceMap": false, // 生产一般不需要 source map(调试用可保留) "declaration": true, // 生成 .d.ts(库项目) "declarationMap": false, // 库项目可开启 // 其他 "esModuleInterop": true, "skipLibCheck": true, // 提升编译速度(生产不建议 false) "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "**/*.test.ts"] }4.2tsc --noEmit与tsc --build
--noEmit:只做类型检查,不输出 JS。CI 中用于验证类型正确性--build(项目引用):增量编译,大型 monorepo 必备
4.3 项目引用(Project References)
拆分大型项目为多个子项目,加速编译:
json
// tsconfig.base.json { "compilerOptions": { "composite": true, "declaration": true } } // packages/core/tsconfig.json { "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist" }, "references": [] // 无依赖 } // packages/app/tsconfig.json { "extends": "../../tsconfig.base.json", "references": [{ "path": "../core" }] }构建命令:tsc --build packages/core packages/app。
五、类型性能优化
大型项目编译慢,往往是因为复杂类型导致 TS 服务器负担过重。
5.1 避免过度条件类型
typescript
// ❌ 差:深度嵌套条件类型 type DeepType<T> = T extends string ? T extends `${infer A}.${infer B}` ? A extends 'a' ? ... : ... : ... : ...; // ✅ 好:拆分为多个辅助类型 + 使用映射类型代替条件递归5.2 使用type代替interface的时机
interface适合声明对象形状、类、可合并扩展type适合联合、交叉、映射、条件类型优先使用
interface直到需要type的特性
5.3 减少递归深度
默认递归深度限制为 1000,深度递归类型会导致编译失败或极慢。可用尾递归优化模式(但 TS 不支持真正的尾递归消除,需手动展平)。
5.4 避免any泄漏
any会关闭类型检查并传播。使用unknown+ 类型守卫代替。
5.5 性能度量
bash
# 生成性能追踪 tsc --generateTrace trace # 上传到 https://www.typescriptlang.org/play?#code/ 分析热路径
六、装饰器(现代 TypeScript)
TypeScript 5.0+ 支持Stage 3 装饰器标准(与之前实验性装饰器不兼容)。
6.1 标准装饰器基本用法
typescript
function logged<This, Args extends any[], Return>( target: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return> ) { return function(this: This, ...args: Args): Return { console.log(`Calling ${String(context.name)} with`, args); return target.call(this, ...args); }; } class Example { @logged greet(name: string) { return `Hello, ${name}`; } }6.2 生产级装饰器场景:依赖注入、日志、性能监控
typescript
// 简单的性能计时装饰器 function time<This, Args extends any[], Return>( fn: (this: This, ...args: Args) => Return, ctx: ClassMethodDecoratorContext ) { return function(this: This, ...args: Args): Return { const start = performance.now(); const result = fn.call(this, ...args); const end = performance.now(); console.log(`${String(ctx.name)} took ${end - start}ms`); return result; }; }注意:标准装饰器不能改变方法签名,不能直接修改类结构(除非返回一个新的类)。
七、与 React/Vue 生产级整合要点
7.1 React + TypeScript 最佳实践
tsx
// 组件 props 类型定义 interface ButtonProps { children: React.ReactNode; onClick?: () => void; variant?: 'primary' | 'secondary'; } // 使用 FC 或显式声明 const Button = ({ children, onClick, variant = 'primary' }: ButtonProps) => { return <button onClick={onClick} className={variant}>{children}</button>; }; // 事件处理类型 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { console.log(e.target.value); }; // useRef 用于 DOM const inputRef = useRef<HTMLInputElement>(null);7.2 Vue 3 + TypeScript
vue
<script setup lang="ts"> interface Props { title: string; count?: number; } const props = withDefaults(defineProps<Props>(), { count: 0 }); const emit = defineEmits<{ (e: 'update', value: number): void; (e: 'close'): void; }>(); </script>八、TypeScript 编译流程与生态系统工具
8.1ts-nodevstsxvstsimp
| 工具 | 特点 | 适用场景 |
|---|---|---|
ts-node | 老牌,支持swc加速 | 开发脚本 |
tsx(基于 esbuild) | 极快,开箱即用 | 开发、工具链 |
tsimp(基于 swc) | 快速,TypeScript 官方推荐实验性 | ESM 项目 |
生产环境永远先编译再运行:tsc→node dist/index.js。
8.2 类型定义发布
库项目必须生成.d.ts:
json
// package.json { "types": "./dist/index.d.ts", "exports": { ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" } } }8.3 Monorepo 工具链
pnpm workspace + tsc --build(轻量)
Nx(企业级)
Turborepo(与 tsc 配合良好)
九、实战:实现一个类型安全的 Event Bus
typescript
type EventMap = { 'user-login': { userId: string; timestamp: number }; 'user-logout': { userId: string }; 'data-update': { id: number; payload: unknown }; }; class TypedEventEmitter<T extends Record<string, any>> { private listeners = new Map<keyof T, Set<(data: any) => void>>(); on<K extends keyof T>(event: K, handler: (data: T[K]) => void): void { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()); } this.listeners.get(event)!.add(handler); } emit<K extends keyof T>(event: K, data: T[K]): void { const handlers = this.listeners.get(event); if (handlers) { handlers.forEach(handler => handler(data)); } } } const emitter = new TypedEventEmitter<EventMap>(); emitter.on('user-login', (data) => { console.log(data.userId, data.timestamp); // ✅ 类型安全 }); emitter.emit('user-login', { userId: '123', timestamp: Date.now() });十、总结:生产级 TypeScript 检查清单
| 类别 | 检查项 | 验收动作 |
|---|---|---|
| 类型安全 | 开启strict全家桶;无隐式any | 运行tsc --noEmit通过 |
| 代码质量 | 启用noUnusedLocals、noImplicitReturns | 确保无未使用变量 |
| 构建 | 配置合理target、module;使用outDir | tsc输出正确 |
| 性能 | 避免深度条件类型递归;使用项目引用 | 编译时间 < 10s(中型项目) |
| 工具链 | 生产环境编译后运行;使用tsc --build增量构建 | CI 中类型检查步骤 |
| 依赖 | 谨慎使用any;尽量用unknown+ 守卫 | 代码库any数量 < 10 |