上篇我们完成了 TypeScript 基础:类型注解、接口、泛型、基本工具类型。
但"会用"和"精通 "之间,横亘着类型编程的深水区。
这篇将深入 生产级 TypeScript 的核心实战------没有基础语法,不注水,全是硬核干货。
一、类型守卫与自定义守卫:让类型系统真正"懂你"
1.1 内置类型守卫
TypeScript 内置了四种类型守卫:
-
typeof:区分string、number、boolean、symbol、bigint、function、object -
instanceof:区分 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.1 Awaited<T>(TypeScript 4.5+)
解开 Promise 的嵌套:
typescript
type Result = Awaited<Promise<Promise<number>>>; // number
3.2 NoInfer<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.2 tsc --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.1 ts-node vs tsx vs tsimp
| 工具 | 特点 | 适用场景 |
|---|---|---|
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 |