Skip to content

性能优化建议

概述

TypeScript 项目的性能优化主要涉及编译性能、类型检查性能和运行时性能三个方面。虽然 TypeScript 是编译时类型系统,但合理的配置和编码实践可以显著提升开发体验和构建速度。本文档介绍了一系列性能优化的技巧和最佳实践,帮助你构建更高效的 TypeScript 项目。

编译性能优化

使用项目引用(Project References)

项目引用(Project References)是 TypeScript 3.0+ 引入的功能,可以将大型项目拆分为多个子项目,实现增量编译:

typescript
// tsconfig.json(根配置)
{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true
  },
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/utils" },
    { "path": "./packages/app" }
  ]
}

// packages/core/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "declaration": true
  }
}

// packages/app/tsconfig.json
{
  "compilerOptions": {
    "composite": true
  },
  "references": [
    { "path": "../core" },
    { "path": "../utils" }
  ]
}

优势

  • 只编译修改过的项目
  • 并行编译独立项目
  • 更好的代码组织和模块化

提示

使用 tsc --build 命令来构建使用项目引用的项目,它会自动处理依赖关系。

启用增量编译

启用增量编译可以让 TypeScript 记住上次编译的结果,只编译变更的文件:

json
// tsconfig.json
{
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": "./.tsbuildinfo"
  }
}

说明

  • incremental: true:启用增量编译
  • tsBuildInfoFile:指定构建信息文件位置(可选,默认在输出目录)

使用 skipLibCheck 跳过库类型检查

对于第三方库的类型定义文件,可以跳过检查以提升编译速度:

json
// tsconfig.json
{
  "compilerOptions": {
    "skipLibCheck": true
  }
}

注意

启用 skipLibCheck 会跳过所有 .d.ts 文件的类型检查,包括你项目中的声明文件。确保你的类型定义是正确的。

优化编译目标

选择合适的编译目标可以减少编译工作量:

json
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",  // 使用较新的目标,减少转换
    "module": "ESNext",  // 使用现代模块系统
    "lib": ["ES2020"]    // 只包含需要的库类型
  }
}

建议

  • 如果目标环境支持,使用较新的 ES 版本
  • 只包含项目实际使用的库类型(lib 选项)

使用 isolatedModules

启用 isolatedModules 可以确保每个文件可以独立编译,提升并行编译效率:

json
// tsconfig.json
{
  "compilerOptions": {
    "isolatedModules": true
  }
}

优势

  • 支持更好的并行编译
  • 与打包工具(如 webpack、rollup)兼容性更好
  • 强制每个文件都是独立的模块

类型检查性能优化

避免深度嵌套的类型

深度嵌套的类型会增加类型检查的计算复杂度:

typescript
// ❌ 不推荐:深度嵌套
type DeepNested = {
  level1: {
    level2: {
      level3: {
        level4: {
          value: string;
        };
      };
    };
  };
};

// ✅ 推荐:使用类型别名分解
type Level4 = {
  value: string;
};

type Level3 = {
  level4: Level4;
};

type Level2 = {
  level3: Level3;
};

type DeepNested = {
  level1: Level2;
};

避免过度使用条件类型

复杂的条件类型会增加类型检查时间,特别是递归条件类型:

typescript
// ❌ 不推荐:过度复杂的条件类型
type DeepFlatten<T> = T extends (infer U)[]
  ? U extends any[]
    ? DeepFlatten<U>
    : U
  : T;

// ✅ 推荐:限制递归深度或使用更简单的类型
type Flatten<T> = T extends (infer U)[] ? U : T;

// 或者使用工具类型
type FlattenArray<T> = T extends readonly (infer U)[] ? U : never;

使用类型缓存

对于复杂的类型计算,使用类型别名缓存中间结果:

typescript
// ❌ 不推荐:重复计算
function process<T>(data: T) {
  type Keys = keyof T;
  type Values = T[keyof T];
  // 每次调用都重新计算类型
}

// ✅ 推荐:使用类型别名缓存
type UserKeys = keyof User;
type UserValues = User[keyof User];

function process(data: User) {
  // 使用预计算的类型
}

避免循环类型引用

循环类型引用会导致类型检查器陷入无限递归:

typescript
// ❌ 不推荐:循环引用
interface A {
  b: B;
}

interface B {
  a: A;
}

// ✅ 推荐:使用可选属性或联合类型打破循环
interface A {
  b?: B;  // 可选,打破循环
}

interface B {
  a?: A;
}

// 或者使用联合类型
type Node = {
  value: string;
  children?: Node[];  // 自引用,但通过数组打破直接循环
};

限制类型推断复杂度

对于复杂的泛型函数,显式指定类型参数可以减少类型推断的工作量:

typescript
// ❌ 不推荐:让 TypeScript 推断复杂类型
const result = complexFunction(data);

// ✅ 推荐:显式指定类型
const result: ExpectedType = complexFunction<ExpectedType>(data);

项目配置优化

使用路径映射(Path Mapping)

路径映射可以减少模块解析的时间:

json
// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@utils/*": ["src/utils/*"],
      "@types/*": ["src/types/*"]
    }
  }
}

优势

  • 更快的模块解析
  • 更清晰的导入路径
  • 更好的代码组织

合理配置 include 和 exclude

只包含需要编译的文件,排除不必要的文件:

json
// tsconfig.json
{
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "**/*.test.ts",
    "**/*.spec.ts"
  ]
}

使用声明文件(.d.ts)

对于不需要编译的纯类型文件,使用 .d.ts 声明文件:

typescript
// types/api.d.ts(只包含类型,不编译)
export interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';

优势

  • 不参与编译,减少编译时间
  • 只用于类型检查
  • 文件体积更小

代码组织优化

按需导入

使用按需导入而不是导入整个模块:

typescript
// ❌ 不推荐:导入整个模块
import * as utils from './utils';

// ✅ 推荐:按需导入
import { formatDate, parseNumber } from './utils';

避免大型单文件

将大型文件拆分为多个小文件:

typescript
// ❌ 不推荐:单个大文件(1000+ 行)
// types.ts - 包含所有类型定义

// ✅ 推荐:按功能拆分
// types/user.ts
export interface User { ... }

// types/product.ts
export interface Product { ... }

// types/order.ts
export interface Order { ... }

使用 barrel exports 谨慎

Barrel exports(索引文件导出)虽然方便,但会影响编译性能:

typescript
// ❌ 不推荐:大型 barrel export
// index.ts
export * from './user';
export * from './product';
export * from './order';
// ... 导出 100+ 个模块

// ✅ 推荐:按需导出或使用命名空间
// index.ts
export { User, UserProfile } from './user';
export { Product, ProductCategory } from './product';

提示

如果必须使用 barrel exports,考虑按功能分组,创建多个索引文件。

工具和构建优化

使用 SWC 或 esbuild 替代 tsc

对于大型项目,可以考虑使用更快的编译器:

json
// package.json
{
  "scripts": {
    "build": "tsc && vite build",
    "build:fast": "swc src -d dist"
  },
  "devDependencies": {
    "@swc/cli": "^0.1.0",
    "@swc/core": "^1.0.0"
  }
}

说明

  • SWC:用 Rust 编写的 TypeScript/JavaScript 编译器,速度极快
  • esbuild:Go 编写的打包工具,支持 TypeScript
  • 这些工具通常只做转译,不做类型检查,需要配合 tsc --noEmit 进行类型检查

使用并行构建

对于多包项目,使用并行构建工具:

json
// package.json
{
  "scripts": {
    "build": "turbo run build",
    "build:parallel": "npm-run-all --parallel build:core build:utils build:app"
  }
}

启用缓存

使用构建工具缓存功能:

json
// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    // 启用缓存
    cacheDir: 'node_modules/.vite'
  }
});

运行时性能优化

虽然 TypeScript 是编译时类型系统,但生成的 JavaScript 代码质量会影响运行时性能:

避免不必要的类型转换

typescript
// ❌ 不推荐:不必要的类型转换
function process(data: any) {
  const user = data as User;
  return user.name;
}

// ✅ 推荐:使用类型守卫
function isUser(data: unknown): data is User {
  return typeof data === 'object' && data !== null && 'name' in data;
}

function process(data: unknown) {
  if (isUser(data)) {
    return data.name;  // 类型安全,无运行时开销
  }
  throw new Error('Invalid user data');
}

使用 const 断言

使用 as const 可以让 TypeScript 生成更优化的代码:

typescript
// ✅ 推荐:使用 const 断言
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
} as const;

// 生成的代码更简洁,没有额外的类型转换

避免使用装饰器的运行时开销

如果不需要装饰器的运行时功能,考虑使用编译时工具:

typescript
// 如果只需要类型检查,不使用装饰器
// 或者使用编译时移除装饰器的工具

监控和测量

测量编译时间

使用工具测量编译时间,找出性能瓶颈:

bash
# 使用 time 命令测量
time tsc

# 使用 TypeScript 的详细输出
tsc --extendedDiagnostics

分析类型检查时间

使用 TypeScript 编译器 API 分析类型检查性能:

typescript
import * as ts from 'typescript';

const program = ts.createProgram(['src/**/*.ts'], {
  // 配置选项
});

const start = Date.now();
const diagnostics = ts.getPreEmitDiagnostics(program);
const end = Date.now();

console.log(`类型检查耗时: ${end - start}ms`);
console.log(`错误数量: ${diagnostics.length}`);

最佳实践总结

实践 1:渐进式优化

提示

不要过早优化。先确保代码正确,再考虑性能优化。使用性能分析工具找出真正的瓶颈。

实践 2:平衡类型安全和性能

typescript
// 在类型安全和性能之间找到平衡
// 对于性能关键路径,可以考虑:
// 1. 使用更简单的类型
// 2. 减少类型检查复杂度
// 3. 使用类型断言(谨慎使用)

实践 3:定期重构

定期重构类型定义,保持类型系统简洁高效:

typescript
// 定期检查:
// 1. 是否有未使用的类型
// 2. 是否有可以简化的复杂类型
// 3. 是否有循环依赖

注意事项

注意

  1. 不要过度优化:过早优化是万恶之源,先确保代码正确性
  2. 保持类型安全:性能优化不应以牺牲类型安全为代价
  3. 测量实际影响:使用工具测量优化前后的实际性能差异
  4. 考虑团队协作:某些优化可能影响代码可读性,需要团队共识

提示

  • 对于小型项目,性能优化可能不是主要关注点
  • 对于大型项目,合理的配置和代码组织可以显著提升开发体验
  • 使用项目引用和增量编译是提升大型项目性能的最有效方法

相关链接

基于 VitePress 构建