TypeScript性能优化实战:从编译时到运行时的5种生产模式

前端工程

你的TypeScript项目正在慢性死亡

10万行代码的TypeScript项目,tsc一次要45秒,HMR刷新卡3秒,打包产物2.4MB,线上首屏3.8秒——这不是个例,这是2026年大多数TypeScript项目的真实写照。类型系统本该是开发效率的倍增器,却变成了性能杀手。Project References没人配,skipLibCheck不敢开,泛型嵌套五层,运行时type guard写在热路径上,bundle里塞满了没tree-shake掉的死代码。

本文将从核心概念出发,带你完成类型检查加速→构建优化→运行时性能→内存效率→生产监控的5种生产模式,从编译时到运行时,从开发体验到线上指标,一步不落。


核心概念

概念 说明
Type Checking Speed TypeScript编译器对类型进行检查的速度,受项目结构、类型复杂度、配置影响
Project References 将大型项目拆分为多个子项目,实现增量编译和并行类型检查
Incremental Build 利用缓存只重新检查变更部分,避免全量类型检查
Tree-Shaking 构建工具识别并移除未引用代码,减少最终bundle体积
Structural Typing TypeScript的结构化类型系统,通过形状而非名义进行类型兼容判断
Branded Types 通过唯一标记创建名义类型,在结构类型系统中实现类型隔离
Const Assertions as const断言,将值推断为最窄的字面量类型,减少类型空间膨胀
Performance Budget 为bundle大小、编译时间等设定预算阈值,CI中自动拦截性能退化

优化流程

编译时优化:
项目拆分(Project References) → 增量编译(Incremental) → skipLibCheck → 隔离模块编译

构建时优化:
tsup/esbuild替代tsc → Tree-Shaking → Code Splitting → Bundle压缩

运行时优化:
避免热路径type guard → 高效泛型 → 内联类型断言 → 减少运行时类型检查

内存优化:
结构类型复用 → Branded Types替代类继承 → const assertions → 类型收窄

生产监控:
类型覆盖率 → Bundle分析 → Performance Budget → CI集成

问题分析:TypeScript性能的5大瓶颈

  1. 类型检查速度慢:单一大项目全量tsc耗时随代码量线性增长,10万行代码项目tsc需30-60秒,CI流水线被类型检查阻塞
  2. 构建产物臃肿:tsc只做类型检查不做tree-shaking,barrel exports导致大量死代码进入bundle,运行时类型守卫代码膨胀
  3. 运行时性能损耗:热路径上频繁的typeof/instanceof检查,深层泛型实例化产生的运行时开销,过度使用class装饰器
  4. 内存效率低下:复杂类型推断占用大量编译器内存,运行时大量相似结构类型创建重复对象,未使用const assertion导致类型空间膨胀
  5. 缺乏性能监控:没有类型覆盖率指标,bundle大小无预算控制,编译时间退化无感知,线上性能问题无法回溯到类型设计

分步实操:5种生产模式

模式1:类型检查速度优化

// tsconfig.base.json - 基础配置
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "skipLibCheck": true,
    "incremental": true,
    "tsBuildInfoFile": ".tsbuildinfo",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true
  }
}

// packages/core/tsconfig.json - 核心包配置
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src"],
  "references": [
    { "path": "../shared" }
  ]
}

// packages/shared/tsconfig.json - 共享包配置
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "composite": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src"]
}

// packages/app/tsconfig.json - 应用包配置
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "noEmit": true
  },
  "include": ["src"],
  "references": [
    { "path": "../core" },
    { "path": "../shared" }
  ]
}

// tsconfig.json - 顶层解决方案配置
{
  "files": [],
  "references": [
    { "path": "packages/shared" },
    { "path": "packages/core" },
    { "path": "packages/app" }
  ]
}
// scripts/typecheck.ts - 增量类型检查脚本
import { execSync } from 'child_process';
import { readFileSync, existsSync, unlinkSync } from 'fs';
import { performance } from 'perf_hooks';

interface TypeCheckResult {
  project: string;
  duration: number;
  errors: number;
  cached: boolean;
}

function typecheckProject(projectPath: string, clean: boolean = false): TypeCheckResult {
  const buildInfoFile = `${projectPath}/.tsbuildinfo`;
  const wasCached = existsSync(buildInfoFile) && !clean;

  if (clean && existsSync(buildInfoFile)) {
    unlinkSync(buildInfoFile);
  }

  const start = performance.now();
  let errors = 0;

  try {
    execSync(`npx tsc --build ${projectPath} --verbose`, {
      encoding: 'utf-8',
      stdio: 'pipe',
    });
  } catch (error: any) {
    const output = error.stdout || error.stderr || '';
    const errorMatches = output.match(/error TS\d+:/g);
    errors = errorMatches ? errorMatches.length : 0;
  }

  const duration = performance.now() - start;

  return {
    project: projectPath,
    duration: Math.round(duration),
    errors,
    cached: wasCached,
  };
}

function runFullTypeCheck(clean: boolean = false): void {
  const projects = [
    'packages/shared',
    'packages/core',
    'packages/app',
  ];

  console.log('=== TypeScript Type Check Report ===\n');

  let totalDuration = 0;
  let totalErrors = 0;

  for (const project of projects) {
    const result = typecheckProject(project, clean);
    totalDuration += result.duration;
    totalErrors += result.errors;

    const status = result.errors === 0 ? '✓' : '✗';
    const cacheStatus = result.cached ? '(cached)' : '(full)';
    console.log(
      `${status} ${result.project}: ${result.duration}ms ${cacheStatus}, ${result.errors} errors`
    );
  }

  console.log(`\nTotal: ${totalDuration}ms, ${totalErrors} errors`);

  if (totalDuration > 10000) {
    console.warn('\n⚠️  Type check exceeds 10s budget! Consider:');
    console.warn('  - Adding more project references');
    console.warn('  - Enabling skipLibCheck');
    console.warn('  - Using isolatedModules');
  }
}

const isClean = process.argv.includes('--clean');
runFullTypeCheck(isClean);
// src/types/performance-optimized.ts - 优化类型定义减少检查负担

// ❌ 复杂的条件类型导致类型检查缓慢
type DeepPartial<T> = T extends object
  ? { [K in keyof T]?: DeepPartial<T[K]> }
  : T;

type DeepRequired<T> = T extends object
  ? { [K in keyof T]-?: DeepRequired<T[K]> }
  : T;

// ✅ 使用工具类型缓存,避免重复实例化
type CachedPartial<T> = Partial<T>;
type CachedRequired<T> = Required<T>;

// ❌ 递归类型深度过大
type FlattenDeep<T> = T extends Array<infer U>
  ? FlattenDeep<U>
  : T;

// ✅ 限制递归深度
type FlattenN<T, N extends number, D extends any[] = []> =
  D['length'] extends N
    ? T
    : T extends Array<infer U>
      ? FlattenN<U, N, [...D, 1]>
      : T;

type Flatten3<T> = FlattenN<T, 3>;

// ❌ 过度使用模板字面量类型
type Route = `/api/${string}/${string}`;

// ✅ 使用联合类型替代模板字面量
type ApiRoute =
  | '/api/users'
  | '/api/users/:id'
  | '/api/posts'
  | '/api/posts/:id'
  | '/api/comments'
  | '/api/comments/:id';

// ❌ 大型联合类型
type Status = 'pending' | 'processing' | 'approved' | 'rejected' | 'cancelled' | 'refunded' | 'completed' | 'archived' | 'draft' | 'published';

// ✅ 分组联合类型
type OrderStatus = 'pending' | 'processing' | 'completed' | 'cancelled';
type ContentStatus = 'draft' | 'published' | 'archived';
type PaymentStatus = 'approved' | 'rejected' | 'refunded';

// skipLibCheck优化的.d.ts
declare module 'heavy-lib' {
  export interface HeavyConfig {
    readonly apiKey: string;
    readonly endpoint: string;
    readonly timeout: number;
    readonly retries: number;
  }

  export function createClient(config: HeavyConfig): HeavyClient;

  export interface HeavyClient {
    getData<T>(id: string): Promise<T>;
    setData<T>(id: string, data: T): Promise<void>;
  }
}

模式2:构建优化与tsup/esbuild

// tsup.config.ts - tsup构建配置
import { defineConfig } from 'tsup';

export default defineConfig([
  {
    entry: ['src/index.ts'],
    format: ['esm', 'cjs'],
    dts: {
      resolve: true,
      compilerOptions: {
        skipLibCheck: true,
        composite: false,
      },
    },
    splitting: true,
    treeshake: true,
    minify: true,
    sourcemap: true,
    clean: true,
    outDir: 'dist',
    target: 'es2022',
    platform: 'node',
    external: ['react', 'react-dom'],
    esbuildOptions(options) {
      options.logLevel = 'info';
      options.chunkNames = 'chunks/[name]-[hash]';
    },
  },
  {
    entry: ['src/browser.ts'],
    format: ['esm'],
    treeshake: true,
    minify: true,
    sourcemap: true,
    outDir: 'dist/browser',
    target: 'es2022',
    platform: 'browser',
    esbuildOptions(options) {
      options.conditions = ['browser'];
    },
  },
]);
// src/index.ts - 优化导出结构以支持tree-shaking

// ❌ barrel export导致tree-shaking失效
// export * from './utils';
// export * from './validators';
// export * from './transformers';

// ✅ 显式命名导出,便于bundler分析依赖
export { validateEmail, validateUrl } from './validators/email';
export { validateAge, validateRange } from './validators/number';
export { formatDate, parseDate } from './transformers/date';
export { formatCurrency, parseCurrency } from './transformers/currency';

// ✅ 使用条件导出减少浏览器bundle
export type { UserSchema, CreateUserInput } from './types/user';
export type { PaginationParams, SearchParams } from './types/pagination';
// scripts/bundle-analysis.ts - Bundle分析脚本
import { build } from 'esbuild';
import { readFileSync, writeFileSync } from 'fs';
import { gzipSync } from 'zlib';

interface BundleMetrics {
  name: string;
  size: number;
  gzipSize: number;
  modules: number;
}

const BUDGET = {
  maxBundleSize: 100 * 1024,
  maxGzipSize: 30 * 1024,
  maxChunkSize: 50 * 1024,
};

async function analyzeBundle(entryPoint: string): Promise<BundleMetrics> {
  const result = await build({
    entryPoints: [entryPoint],
    bundle: true,
    minify: true,
    write: false,
    metafile: true,
    format: 'esm',
    target: 'es2022',
    external: ['react', 'react-dom'],
  });

  const outputFiles = result.outputFiles || [];
  const metafile = result.metafile;

  let totalSize = 0;
  for (const file of outputFiles) {
    totalSize += file.contents.byteLength;
  }

  const gzipSize = gzipSync(Buffer.from(outputFiles[0]?.contents || [])).byteLength;

  const moduleCount = metafile ? Object.keys(metafile.inputs).length : 0;

  return {
    name: entryPoint,
    size: totalSize,
    gzipSize,
    modules: moduleCount,
  };
}

async function runBundleAnalysis(): Promise<void> {
  console.log('=== Bundle Analysis Report ===\n');

  const entryPoints = [
    'src/index.ts',
    'src/browser.ts',
  ];

  let hasViolation = false;

  for (const entry of entryPoints) {
    const metrics = await analyzeBundle(entry);

    const sizeKB = (metrics.size / 1024).toFixed(1);
    const gzipKB = (metrics.gzipSize / 1024).toFixed(1);

    console.log(`${entry}:`);
    console.log(`  Size: ${sizeKB}KB (budget: ${BUDGET.maxBundleSize / 1024}KB)`);
    console.log(`  Gzip: ${gzipKB}KB (budget: ${BUDGET.maxGzipSize / 1024}KB)`);
    console.log(`  Modules: ${metrics.modules}`);

    if (metrics.size > BUDGET.maxBundleSize) {
      console.warn(`  ⚠️  Bundle size exceeds budget!`);
      hasViolation = true;
    }

    if (metrics.gzipSize > BUDGET.maxGzipSize) {
      console.warn(`  ⚠️  Gzip size exceeds budget!`);
      hasViolation = true;
    }

    console.log();
  }

  if (hasViolation) {
    console.error('❌ Performance budget violated!');
    process.exit(1);
  } else {
    console.log('✅ All bundles within budget.');
  }
}

runBundleAnalysis();
// package.json - 构建脚本优化
{
  "scripts": {
    "typecheck": "tsc --build",
    "typecheck:clean": "tsc --build --clean && tsc --build",
    "build": "tsup",
    "build:analyze": "tsup --metafile && node scripts/bundle-analysis.ts",
    "dev": "tsup --watch",
    "check:all": "npm run typecheck && npm run build && npm run test",
    "prepack": "npm run build"
  }
}

模式3:运行时性能优化

// src/runtime/hot-path.ts - 热路径类型守卫优化

// ❌ 在热路径上使用复杂类型守卫
function processData(data: unknown): ProcessedData {
  if (
    typeof data === 'object' &&
    data !== null &&
    'id' in data &&
    typeof (data as any).id === 'string' &&
    'name' in data &&
    typeof (data as any).name === 'string' &&
    'age' in data &&
    typeof (data as any).age === 'number' &&
    'email' in data &&
    typeof (data as any).email === 'string'
  ) {
    return data as ProcessedData;
  }
  throw new Error('Invalid data');
}

// ✅ 最小化类型守卫,信任编译时类型
interface ProcessedData {
  id: string;
  name: string;
  age: number;
  email: string;
}

function processDataOptimized(data: unknown): ProcessedData {
  if (typeof data !== 'object' || data === null) {
    throw new Error('Expected object');
  }
  return data as ProcessedData;
}

// ✅ 分层校验:热路径快速检查,冷路径严格校验
function processHotPath(data: unknown): ProcessedData {
  if (typeof data !== 'object' || data === null) {
    throw new Error('Expected object');
  }
  return data as ProcessedData;
}

function processColdPath(data: unknown): ProcessedData {
  const result = ProcessedDataSchema.safeParse(data);
  if (!result.success) {
    throw new Error(`Validation failed: ${result.error.message}`);
  }
  return result.data;
}

import { z } from 'zod';

const ProcessedDataSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1),
  age: z.number().int().min(0),
  email: z.string().email(),
});
// src/runtime/generics.ts - 高效泛型模式

// ❌ 泛型实例化导致运行时膨胀
class DataStore<T> {
  private items: T[] = [];

  add(item: T): void {
    this.items.push(item);
  }

  get(index: number): T | undefined {
    return this.items[index];
  }

  filter(predicate: (item: T) => boolean): T[] {
    return this.items.filter(predicate);
  }

  map<U>(transform: (item: T) => U): U[] {
    return this.items.map(transform);
  }

  reduce<U>(reducer: (acc: U, item: T) => U, initial: U): U {
    return this.items.reduce(reducer, initial);
  }
}

// ✅ 使用函数式工具替代泛型类
interface DataStore {
  readonly items: readonly unknown[];
}

function createStore<T>(initialItems: T[] = []): DataStore {
  return { items: Object.freeze([...initialItems]) };
}

function addItem<T>(store: DataStore, item: T): DataStore {
  return { items: Object.freeze([...store.items, item]) };
}

function getItem<T>(store: DataStore, index: number): T {
  return store.items[index] as T;
}

function filterItems<T>(store: DataStore, predicate: (item: T) => boolean): T[] {
  return store.items.filter(predicate as (item: unknown) => boolean) as T[];
}

// ✅ 使用条件类型替代运行时分支
type Result<T, E = Error> =
  | { readonly ok: true; readonly value: T }
  | { readonly ok: false; readonly error: E };

function ok<T>(value: T): Result<T> {
  return { ok: true, value };
}

function err<E>(error: E): Result<never, E> {
  return { ok: false, error };
}

function mapResult<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> {
  return result.ok ? ok(fn(result.value)) : result;
}

function flatMapResult<T, U, E>(
  result: Result<T, E>,
  fn: (value: T) => Result<U, E>
): Result<U, E> {
  return result.ok ? fn(result.value) : result;
}

// ✅ 避免运行时类型擦除的开销
interface TypeTag<T extends string> {
  readonly __type: T;
}

type UserId = string & TypeTag<'UserId'>;
type OrderId = string & TypeTag<'OrderId'>;

function createUserId(id: string): UserId {
  return id as UserId;
}

function createOrderId(id: string): OrderId {
  return id as OrderId;
}

function getUser(id: UserId): Promise<User | null> {
  return Promise.resolve(null);
}

function getOrder(id: OrderId): Promise<Order | null> {
  return Promise.resolve(null);
}

interface User {
  id: UserId;
  name: string;
}

interface Order {
  id: OrderId;
  total: number;
}
// src/runtime/iteration.ts - 高效迭代模式

// ❌ 每次迭代创建新数组
function processUsers(users: User[]): ProcessedUser[] {
  return users
    .filter((u) => u.age >= 18)
    .map((u) => ({ ...u, displayName: u.name.toUpperCase() }))
    .filter((u) => u.displayName.length > 0)
    .map((u) => ({ ...u, score: calculateScore(u) }));
}

// ✅ 单次遍历替代多次链式调用
function processUsersOptimized(users: User[]): ProcessedUser[] {
  const result: ProcessedUser[] = [];

  for (const u of users) {
    if (u.age < 18) continue;

    const displayName = u.name.toUpperCase();
    if (displayName.length === 0) continue;

    result.push({
      ...u,
      displayName,
      score: calculateScore(u),
    });
  }

  return result;
}

// ✅ 使用生成器延迟计算
function* filterUsers(users: Iterable<User>): Generator<User> {
  for (const u of users) {
    if (u.age >= 18) yield u;
  }
}

function* mapUsers(users: Iterable<User>): Generator<ProcessedUser> {
  for (const u of users) {
    yield {
      ...u,
      displayName: u.name.toUpperCase(),
      score: calculateScore(u),
    };
  }
}

function processUsersLazy(users: User[]): ProcessedUser[] {
  return [...mapUsers(filterUsers(users))];
}

interface ProcessedUser extends User {
  displayName: string;
  score: number;
}

function calculateScore(user: User): number {
  return user.name.length * user.age;
}

模式4:内存效率优化

// src/memory/structural-typing.ts - 结构类型复用

// ❌ 每个接口独立定义,编译器为每个创建独立类型
interface UserResponse {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'editor' | 'viewer';
  createdAt: string;
}

interface UserListItem {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'editor' | 'viewer';
  createdAt: string;
}

interface UserProfile {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'editor' | 'viewer';
  createdAt: string;
  bio: string;
  avatar: string;
}

// ✅ 使用基础类型+组合复用
interface BaseEntity {
  readonly id: string;
  readonly createdAt: string;
  readonly updatedAt: string;
}

type UserRole = 'admin' | 'editor' | 'viewer';

interface UserBase extends BaseEntity {
  readonly name: string;
  readonly email: string;
  readonly role: UserRole;
}

type UserResponse = UserBase;
type UserListItem = UserBase;

interface UserProfile extends UserBase {
  readonly bio: string;
  readonly avatar: string;
}

// ✅ 使用Pick/Omit从基础类型派生
type UserSummary = Pick<UserBase, 'id' | 'name' | 'role'>;
type UserEmailInfo = Pick<UserBase, 'id' | 'email'>;
type UserWithoutRole = Omit<UserBase, 'role'>;
// src/memory/branded-types.ts - Branded Types替代类继承

// ❌ 使用类继承导致运行时原型链开销
class Entity {
  constructor(
    public id: string,
    public createdAt: Date,
  ) {}
}

class UserEntity extends Entity {
  constructor(
    id: string,
    createdAt: Date,
    public name: string,
    public email: string,
  ) {
    super(id, createdAt);
  }
}

class OrderEntity extends Entity {
  constructor(
    id: string,
    createdAt: Date,
    public total: number,
    public status: string,
  ) {
    super(id, createdAt);
  }
}

// ✅ 使用Branded Types + 纯数据对象
interface Brand<T extends string> {
  readonly __brand: T;
}

type Branded<T, B extends string> = T & Brand<B>;

type UserId = Branded<string, 'UserId'>;
type OrderId = Branded<string, 'OrderId'>;
type ProductId = Branded<string, 'ProductId'>;

function createUserId(id: string): UserId {
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id)) {
    throw new Error('Invalid UUID format for UserId');
  }
  return id as UserId;
}

function createOrderId(id: string): OrderId {
  if (!/^ORD-\d{8}$/.test(id)) {
    throw new Error('Invalid order ID format');
  }
  return id as OrderId;
}

interface User {
  readonly id: UserId;
  readonly name: string;
  readonly email: string;
  readonly role: UserRole;
}

interface Order {
  readonly id: OrderId;
  readonly userId: UserId;
  readonly total: number;
  readonly status: OrderStatus;
  readonly items: ReadonlyArray<OrderItem>;
}

type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';

interface OrderItem {
  readonly productId: ProductId;
  readonly quantity: number;
  readonly unitPrice: number;
}

// ✅ Branded Types确保类型安全,零运行时开销
function getUserOrders(userId: UserId, orders: ReadonlyArray<Order>): ReadonlyArray<Order> {
  return orders.filter((o) => o.userId === userId);
}

// 编译时防止ID混用
// getUserOrders(createOrderId('ORD-20260616'), orders); // ❌ 类型错误
// src/memory/const-assertions.ts - const assertions优化

// ❌ 宽泛类型导致编译器创建大量类型实例
const ROUTES = {
  users: '/api/users',
  posts: '/api/posts',
  comments: '/api/comments',
};

type RouteKey = keyof typeof ROUTES; // string
type RouteValue = typeof ROUTES[keyof typeof ROUTES]; // string

// ✅ const assertion锁定字面量类型
const ROUTES = {
  users: '/api/users',
  posts: '/api/posts',
  comments: '/api/comments',
} as const;

type RouteKey = keyof typeof ROUTES; // 'users' | 'posts' | 'comments'
type RouteValue = typeof ROUTES[keyof typeof ROUTES]; // '/api/users' | '/api/posts' | '/api/comments'

// ✅ const assertion + 满足模式
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'] as const;
type HttpMethod = typeof HTTP_METHODS[number]; // 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'

const STATUS_CODES = {
  OK: 200,
  CREATED: 201,
  BAD_REQUEST: 400,
  UNAUTHORIZED: 401,
  FORBIDDEN: 403,
  NOT_FOUND: 404,
  INTERNAL_ERROR: 500,
} as const;

type StatusCode = typeof STATUS_CODES[keyof typeof STATUS_CODES];
// 200 | 201 | 400 | 401 | 403 | 404 | 500

// ✅ 使用satisfies确保类型正确同时保留字面量类型
const API_CONFIG = {
  baseUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3,
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  },
} as const satisfies Record<string, string | number | Record<string, string>>;

type ApiConfigKey = keyof typeof API_CONFIG; // 'baseUrl' | 'timeout' | 'retries' | 'headers'

// ✅ 冻结对象防止运行时修改
const IMMUTABLE_CONFIG = Object.freeze({
  MAX_RETRIES: 3,
  TIMEOUT_MS: 5000,
  PAGE_SIZE: 20,
  MAX_PAGE_SIZE: 100,
} as const);

type Config = typeof IMMUTABLE_CONFIG;
// src/memory/type-narrowing.ts - 高效类型收窄

// ❌ 多次重复类型守卫
function handleData(data: unknown): string {
  if (typeof data === 'object' && data !== null && 'type' in data) {
    if ((data as any).type === 'user') {
      if (typeof (data as any).name === 'string') {
        return (data as any).name;
      }
    }
    if ((data as any).type === 'order') {
      if (typeof (data as any).id === 'string') {
        return (data as any).id;
      }
    }
  }
  return 'unknown';
}

// ✅ 使用判别联合 + 类型守卫函数
interface UserData {
  readonly type: 'user';
  readonly name: string;
  readonly email: string;
}

interface OrderData {
  readonly type: 'order';
  readonly id: string;
  readonly total: number;
}

type AppData = UserData | OrderData;

function isUserData(data: AppData): data is UserData {
  return data.type === 'user';
}

function isOrderData(data: AppData): data is OrderData {
  return data.type === 'order';
}

function handleDataOptimized(data: AppData): string {
  switch (data.type) {
    case 'user':
      return data.name;
    case 'order':
      return data.id;
  }
}

// ✅ 使用类型谓词缓存
type TypeGuard<T> = (value: unknown) => value is T;

function createTypeGuard<T>(check: (value: unknown) => boolean): TypeGuard<T> {
  return (value: unknown): value is T => check(value);
}

const isString = createTypeGuard<string>((v) => typeof v === 'string');
const isNumber = createTypeGuard<number>((v) => typeof v === 'number');
const isBoolean = createTypeGuard<boolean>((v) => typeof v === 'boolean');

function parseEnvValue<T>(
  value: string | undefined,
  guard: TypeGuard<T>,
  defaultValue: T,
): T {
  if (value === undefined) return defaultValue;
  const parsed: unknown = value;
  return guard(parsed) ? parsed : defaultValue;
}

const port = parseEnvValue(process.env.PORT, isNumber, 3000);
const debug = parseEnvValue(process.env.DEBUG, isBoolean, false);

模式5:生产监控与性能预算

// scripts/type-coverage.ts - 类型覆盖率检查
import { execSync } from 'child_process';

interface TypeCoverageResult {
  totalFiles: number;
  typedFiles: number;
  coverage: number;
  anyCount: number;
  unsafeCount: number;
}

function checkTypeCoverage(): TypeCoverageResult {
  try {
    const output = execSync(
      'npx type-coverage --detail --at-least 95',
      { encoding: 'utf-8', stdio: 'pipe' }
    );

    const match = output.match(/(\d+)\/(\d+) files/);
    const anyMatch = output.match(/(\d+) any/);

    const typedFiles = match ? parseInt(match[1], 10) : 0;
    const totalFiles = match ? parseInt(match[2], 10) : 0;
    const anyCount = anyMatch ? parseInt(anyMatch[1], 10) : 0;

    return {
      totalFiles,
      typedFiles,
      coverage: totalFiles > 0 ? (typedFiles / totalFiles) * 100 : 0,
      anyCount,
      unsafeCount: anyCount,
    };
  } catch {
    return {
      totalFiles: 0,
      typedFiles: 0,
      coverage: 0,
      anyCount: -1,
      unsafeCount: -1,
    };
  }
}

function reportTypeCoverage(): void {
  const result = checkTypeCoverage();

  console.log('=== Type Coverage Report ===\n');
  console.log(`Files: ${result.typedFiles}/${result.totalFiles}`);
  console.log(`Coverage: ${result.coverage.toFixed(1)}%`);
  console.log(`Any count: ${result.anyCount}`);

  if (result.coverage < 95) {
    console.warn('\n⚠️  Type coverage below 95%!');
    console.warn('  Run `npx type-coverage --detail` to find untyped code.');
  }

  if (result.coverage < 80) {
    console.error('❌ Type coverage critically low!');
    process.exit(1);
  }
}

reportTypeCoverage();
// scripts/performance-budget.ts - 性能预算CI检查
import { readFileSync } from 'fs';
import { gzipSync } from 'zlib';

interface PerformanceBudget {
  maxBundleSize: number;
  maxGzipSize: number;
  maxTypeCheckTime: number;
  minTypeCoverage: number;
  maxChunkCount: number;
}

const DEFAULT_BUDGET: PerformanceBudget = {
  maxBundleSize: 100 * 1024,       // 100KB
  maxGzipSize: 30 * 1024,          // 30KB
  maxTypeCheckTime: 15000,         // 15s
  minTypeCoverage: 95,             // 95%
  maxChunkCount: 10,
};

interface BudgetCheckResult {
  metric: string;
  value: number;
  budget: number;
  unit: string;
  passed: boolean;
}

function checkBundleBudget(
  bundlePath: string,
  budget: PerformanceBudget = DEFAULT_BUDGET,
): BudgetCheckResult[] {
  const results: BudgetCheckResult[] = [];

  try {
    const content = readFileSync(bundlePath);
    const size = content.byteLength;
    const gzipSize = gzipSync(content).byteLength;

    results.push({
      metric: 'Bundle Size',
      value: size,
      budget: budget.maxBundleSize,
      unit: 'bytes',
      passed: size <= budget.maxBundleSize,
    });

    results.push({
      metric: 'Gzip Size',
      value: gzipSize,
      budget: budget.maxGzipSize,
      unit: 'bytes',
      passed: gzipSize <= budget.maxGzipSize,
    });
  } catch {
    results.push({
      metric: 'Bundle Size',
      value: -1,
      budget: budget.maxBundleSize,
      unit: 'bytes',
      passed: false,
    });
  }

  return results;
}

function checkTypeCheckBudget(
  budget: PerformanceBudget = DEFAULT_BUDGET,
): BudgetCheckResult {
  const start = Date.now();
  try {
    execSync('npx tsc --noEmit', { encoding: 'utf-8', stdio: 'pipe' });
  } catch {
    // type errors are handled separately
  }
  const duration = Date.now() - start;

  return {
    metric: 'Type Check Time',
    value: duration,
    budget: budget.maxTypeCheckTime,
    unit: 'ms',
    passed: duration <= budget.maxTypeCheckTime,
  };
}

function runPerformanceBudget(): void {
  console.log('=== Performance Budget Check ===\n');

  const results: BudgetCheckResult[] = [
    ...checkBundleBudget('dist/index.js'),
    checkTypeCheckBudget(),
  ];

  let allPassed = true;

  for (const result of results) {
    const status = result.passed ? '✓' : '✗';
    const valueStr = result.value >= 0
      ? result.unit === 'bytes'
        ? `${(result.value / 1024).toFixed(1)}KB`
        : `${result.value}${result.unit}`
      : 'N/A';
    const budgetStr = result.unit === 'bytes'
      ? `${(result.budget / 1024).toFixed(1)}KB`
      : `${result.budget}${result.unit}`;

    console.log(`${status} ${result.metric}: ${valueStr} (budget: ${budgetStr})`);

    if (!result.passed) {
      allPassed = false;
    }
  }

  if (!allPassed) {
    console.error('\n❌ Performance budget violated!');
    process.exit(1);
  } else {
    console.log('\n✅ All performance budgets met.');
  }
}

import { execSync } from 'child_process';
runPerformanceBudget();
// .github/workflows/typescript-perf.yml - CI性能监控
// GitHub Actions工作流配置
/*
name: TypeScript Performance

on:
  pull_request:
    paths:
      - '**.ts'
      - '**.tsx'
      - 'tsconfig*.json'
      - 'package.json'

jobs:
  performance:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - run: npm ci
      - name: Type Check
        run: |
          START=$(date +%s%N)
          npx tsc --noEmit
          END=$(date +%s%N)
          DURATION=$(( (END - START) / 1000000 ))
          echo "Type check duration: ${DURATION}ms"
          if [ $DURATION -gt 15000 ]; then
            echo "::error::Type check exceeds 15s budget (${DURATION}ms)"
            exit 1
          fi

      - name: Type Coverage
        run: |
          npx type-coverage --at-least 95 || {
            echo "::error::Type coverage below 95%"
            exit 1
          }

      - name: Bundle Analysis
        run: |
          npm run build
          node scripts/performance-budget.ts

      - name: Comment PR
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const report = fs.readFileSync('perf-report.txt', 'utf8');
            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: `## 📊 TypeScript Performance Report\n${report}`
            });
*/
// src/monitoring/runtime-metrics.ts - 运行时性能指标收集
interface PerformanceMetric {
  name: string;
  value: number;
  timestamp: number;
  tags: Record<string, string>;
}

class PerformanceCollector {
  private metrics: PerformanceMetric[] = [];
  private readonly maxMetrics = 1000;

  recordMetric(name: string, value: number, tags: Record<string, string> = {}): void {
    this.metrics.push({
      name,
      value,
      timestamp: Date.now(),
      tags,
    });

    if (this.metrics.length > this.maxMetrics) {
      this.metrics = this.metrics.slice(-this.maxMetrics);
    }
  }

  timeAsync<T>(name: string, fn: () => Promise<T>, tags?: Record<string, string>): Promise<T> {
    const start = performance.now();
    return fn().finally(() => {
      const duration = performance.now() - start;
      this.recordMetric(name, duration, { ...tags, unit: 'ms' });
    });
  }

  timeSync<T>(name: string, fn: () => T, tags?: Record<string, string>): T {
    const start = performance.now();
    try {
      return fn();
    } finally {
      const duration = performance.now() - start;
      this.recordMetric(name, duration, { ...tags, unit: 'ms' });
    }
  }

  getMetrics(name?: string): PerformanceMetric[] {
    if (name) {
      return this.metrics.filter((m) => m.name === name);
    }
    return [...this.metrics];
  }

  getAverage(name: string): number {
    const named = this.getMetrics(name);
    if (named.length === 0) return 0;
    return named.reduce((sum, m) => sum + m.value, 0) / named.length;
  }

  getP95(name: string): number {
    const named = this.getMetrics(name);
    if (named.length === 0) return 0;
    const sorted = named.map((m) => m.value).sort((a, b) => a - b);
    const index = Math.ceil(sorted.length * 0.95) - 1;
    return sorted[index];
  }

  generateReport(): string {
    const metricNames = [...new Set(this.metrics.map((m) => m.name))];
    const lines: string[] = ['### Runtime Performance Report\n'];

    for (const name of metricNames) {
      const avg = this.getAverage(name).toFixed(2);
      const p95 = this.getP95(name).toFixed(2);
      const count = this.getMetrics(name).length;
      lines.push(`- **${name}**: avg=${avg}ms, p95=${p95}ms, n=${count}`);
    }

    return lines.join('\n');
  }
}

const perfCollector = new PerformanceCollector();

async function fetchWithMetrics<T>(
  url: string,
  schema: z.ZodSchema<T>,
): Promise<T> {
  return perfCollector.timeAsync(
    'api.fetch',
    async () => {
      const response = await fetch(url);
      const raw = await response.json();
      const result = schema.safeParse(raw);
      if (!result.success) {
        throw new Error(`API validation failed: ${result.error.message}`);
      }
      return result.data;
    },
    { url: new URL(url).pathname },
  );
}

常见陷阱

陷阱1:skipLibCheck导致类型不安全

// ❌ 全局skipLibCheck关闭了所有.d.ts检查
// tsconfig.json
{
  "compilerOptions": {
    "skipLibCheck": true  // 关闭所有库类型检查
  }
}

// ✅ skipLibCheck只跳过node_modules中的.d.ts,自己的.d.ts仍需检查
// tsconfig.json
{
  "compilerOptions": {
    "skipLibCheck": true  // 安全:只影响node_modules
  }
}
// 自己的types目录单独配置strict检查
// types/tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "skipLibCheck": false
  }
}

陷阱2:barrel exports破坏tree-shaking

// ❌ index.ts中re-export所有模块
// src/index.ts
export * from './users';
export * from './orders';
export * from './products';
export * from './analytics';

// ✅ 显式按需导出
// src/index.ts
export { UserService } from './users/service';
export { OrderService } from './orders/service';
export type { User, CreateUserInput } from './users/types';
export type { Order, CreateOrderInput } from './orders/types';

// ✅ package.json中配置sideEffects
// package.json
{
  "sideEffects": false,
  "module": "./dist/index.mjs",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    },
    "./users": {
      "import": "./dist/users.mjs"
    }
  }
}

陷阱3:Project References配置不完整

// ❌ 缺少composite选项导致引用失效
// packages/shared/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist"
    // 缺少 "composite": true
  }
}

// ✅ 被引用的项目必须启用composite
// packages/shared/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src"]
}

陷阱4:运行时过度使用类型守卫

// ❌ 每次循环都做完整类型检查
function processItems(items: unknown[]): Result[] {
  return items.map((item) => {
    const validated = ItemSchema.parse(item);  // 每次都完整校验
    return transform(validated);
  });
}

// ✅ 入口校验一次,内部信任类型
function processItems(items: unknown[]): Result[] {
  const validated = z.array(ItemSchema).parse(items);  // 一次性校验
  return validated.map(transform);
}

// ✅ 或使用分层校验
function processItems(items: unknown[]): Result[] {
  return items.map((item) => {
    if (!isPlainObject(item)) throw new Error('Invalid item');
    return transform(item as Item);
  });
}

function isPlainObject(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

陷阱5:忽略编译器内存限制

// ❌ 大型项目默认1.5GB内存不够
// 运行tsc时内存溢出
// FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

// ✅ 增加Node.js内存限制
// package.json
{
  "scripts": {
    "typecheck": "NODE_OPTIONS='--max-old-space-size=4096' tsc --build",
    "typecheck:ci": "NODE_OPTIONS='--max-old-space-size=8192' tsc --build"
  }
}

// ✅ 拆分项目减少单个编译器内存需求
// 使用Project References将大项目拆分为多个子项目
// 每个子项目独立编译,内存峰值大幅降低

错误排查表

错误现象 可能原因 解决方案
tsc超过30秒 单一大项目无Project References 拆分为多个子项目,启用增量编译
error TS6307 Project References缺少composite 被引用项目添加"composite": true
bundle体积异常大 barrel exports破坏tree-shaking 改为显式命名导出,配置sideEffects
HMR刷新慢 tsc全量重新检查 启用incrementalisolatedModules
FATAL ERROR: Reached heap limit 编译器内存溢出 增加--max-old-space-size或拆分项目
类型推断超时 递归类型深度过大 限制递归深度,使用FlattenN<T, 3>
Type instantiation is excessively deep 条件类型嵌套过深 简化类型逻辑,使用中间类型别名
运行时性能差 热路径频繁类型守卫 分层校验,热路径最小化检查
skipLibCheck后类型错误 自定义.d.ts有问题 自定义types目录单独strict检查
bundle中包含未使用代码 tsconfig importsNotUsedAsValues 使用tsup/esbuild替代tsc打包

高级优化

编译器性能调优

// tsconfig.perf.json - 极致性能配置
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": "./.tsbuildinfo",
    "skipLibCheck": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,
    "assumeChangesOnlyAffectDirectDependencies": true,
    "disableSourceOfProjectReferenceRedirect": true,
    "disableReferencedProjectLoad": true
  }
}

// scripts/tsc-watch.ts - 智能增量监听
import { watch } from 'chokidar';
import { execSync } from 'child_process';
import { relative, dirname, extname } from 'path';
import { performance } from 'perf_hooks';

interface WatchConfig {
  include: string[];
  exclude: string[];
  debounceMs: number;
  projects: Record<string, string[]>;
}

const watchConfig: WatchConfig = {
  include: ['src/**/*.ts'],
  exclude: ['src/**/*.test.ts', 'src/**/*.spec.ts'],
  debounceMs: 300,
  projects: {
    'packages/shared': ['packages/shared/src/**/*.ts'],
    'packages/core': ['packages/core/src/**/*.ts'],
    'packages/app': ['packages/app/src/**/*.ts'],
  },
};

let debounceTimer: NodeJS.Timeout | null = null;

function getAffectedProject(filePath: string): string | null {
  for (const [project, patterns] of Object.entries(watchConfig.projects)) {
    for (const pattern of patterns) {
      const dir = pattern.replace('/**/*.ts', '');
      if (filePath.startsWith(dir)) {
        return project;
      }
    }
  }
  return null;
}

function typecheckAffected(filePath: string): void {
  const project = getAffectedProject(filePath);
  if (!project) return;

  const start = performance.now();
  try {
    execSync(`npx tsc --build ${project}`, { encoding: 'utf-8', stdio: 'pipe' });
    const duration = (performance.now() - start).toFixed(0);
    console.log(`✓ ${project}: ${duration}ms`);
  } catch (error: any) {
    const duration = (performance.now() - start).toFixed(0);
    console.error(`✗ ${project}: ${duration}ms`);
    console.error(error.stdout || error.message);
  }
}

const watcher = watch(watchConfig.include, {
  ignored: watchConfig.exclude,
  persistent: true,
  ignoreInitial: true,
});

watcher.on('change', (filePath) => {
  if (debounceTimer) clearTimeout(debounceTimer);
  debounceTimer = setTimeout(() => {
    typecheckAffected(filePath);
  }, watchConfig.debounceMs);
});

watcher.on('add', (filePath) => {
  typecheckAffected(filePath);
});

console.log('🔍 Watching for TypeScript changes...');

Bundle优化策略

// scripts/advanced-bundle-opt.ts - 高级bundle优化
import { build, Plugin } from 'esbuild';

const sizeLimitPlugin: Plugin = {
  name: 'size-limit',
  setup(build) {
    build.onEnd((result) => {
      for (const output of result.outputFiles || []) {
        const sizeKB = output.contents.byteLength / 1024;
        const path = output.path;

        if (sizeKB > 50) {
          console.warn(`⚠️  ${path}: ${sizeKB.toFixed(1)}KB exceeds 50KB chunk limit`);
        }
      }
    });
  },
};

const deadCodeEliminationPlugin: Plugin = {
  name: 'dead-code-elimination',
  setup(build) {
    build.onEnd((result) => {
      const content = result.outputFiles?.[0]?.text || '';
      const deadCodePatterns = [
        /if\s*\(\s*false\s*\)/g,
        /if\s*\(\s*true\s*\)\s*\{/g,
        /\/\/\s*@ts-ignore/g,
      ];

      for (const pattern of deadCodePatterns) {
        const matches = content.match(pattern);
        if (matches && matches.length > 0) {
          console.warn(`⚠️  Potential dead code: ${matches.length} occurrences of ${pattern}`);
        }
      }
    });
  },
};

async function buildOptimized(): Promise<void> {
  await build({
    entryPoints: ['src/index.ts'],
    bundle: true,
    minify: true,
    treeShaking: true,
    format: 'esm',
    target: 'es2022',
    outdir: 'dist',
    metafile: true,
    splitting: true,
    plugins: [sizeLimitPlugin, deadCodeEliminationPlugin],
    external: ['react', 'react-dom', 'zod'],
    define: {
      'process.env.NODE_ENV': '"production"',
    },
    logLevel: 'info',
  });
}

buildOptimized();

运行时性能Profile

// src/monitoring/profiler.ts - 运行时性能分析器
interface ProfileEntry {
  operation: string;
  startTime: number;
  endTime: number;
  duration: number;
  memoryBefore: number;
  memoryAfter: number;
  memoryDelta: number;
}

class RuntimeProfiler {
  private entries: ProfileEntry[] = [];
  private readonly maxEntries = 500;

  profile<T>(operation: string, fn: () => T): T {
    const memBefore = process.memoryUsage().heapUsed;
    const startTime = performance.now();

    try {
      return fn();
    } finally {
      const endTime = performance.now();
      const memAfter = process.memoryUsage().heapUsed;

      this.entries.push({
        operation,
        startTime,
        endTime,
        duration: endTime - startTime,
        memoryBefore: memBefore,
        memoryAfter: memAfter,
        memoryDelta: memAfter - memBefore,
      });

      if (this.entries.length > this.maxEntries) {
        this.entries = this.entries.slice(-this.maxEntries);
      }
    }
  }

  async profileAsync<T>(operation: string, fn: () => Promise<T>): Promise<T> {
    const memBefore = process.memoryUsage().heapUsed;
    const startTime = performance.now();

    try {
      return await fn();
    } finally {
      const endTime = performance.now();
      const memAfter = process.memoryUsage().heapUsed;

      this.entries.push({
        operation,
        startTime,
        endTime,
        duration: endTime - startTime,
        memoryBefore: memBefore,
        memoryAfter: memAfter,
        memoryDelta: memAfter - memBefore,
      });

      if (this.entries.length > this.maxEntries) {
        this.entries = this.entries.slice(-this.maxEntries);
      }
    }
  }

  getHotspots(thresholdMs: number = 10): ProfileEntry[] {
    return this.entries
      .filter((e) => e.duration > thresholdMs)
      .sort((a, b) => b.duration - a.duration);
  }

  getMemoryHotspots(thresholdBytes: number = 1024 * 100): ProfileEntry[] {
    return this.entries
      .filter((e) => e.memoryDelta > thresholdBytes)
      .sort((a, b) => b.memoryDelta - a.memoryDelta);
  }

  generateReport(): string {
    const lines: string[] = [
      '### Runtime Performance Profile\n',
      '#### Time Hotspots (>10ms)',
    ];

    for (const entry of this.getHotspots()) {
      lines.push(
        `- **${entry.operation}**: ${entry.duration.toFixed(2)}ms, ` +
        `memory: ${(entry.memoryDelta / 1024).toFixed(1)}KB`
      );
    }

    lines.push('\n#### Memory Hotspots (>100KB)');
    for (const entry of this.getMemoryHotspots()) {
      lines.push(
        `- **${entry.operation}**: ${(entry.memoryDelta / 1024).toFixed(1)}KB, ` +
        `time: ${entry.duration.toFixed(2)}ms`
      );
    }

    return lines.join('\n');
  }
}

const profiler = new RuntimeProfiler();

// 使用示例
const users = profiler.profile('fetchUsers', () => {
  return Array.from({ length: 1000 }, (_, i) => ({
    id: `user-${i}`,
    name: `User ${i}`,
    email: `user${i}@example.com`,
  }));
});

const result = profiler.profile('processUsers', () => {
  return users.filter((u) => u.email.includes('example')).map((u) => u.name);
});

console.log(profiler.generateReport());

方案对比

优化维度 tsc增量编译 Project References tsup/esbuild Vite Turbopack
编译速度 ★★★ ★★★★ ★★★★★ ★★★★★ ★★★★★
配置复杂度
增量支持 否(全量但极快)
类型检查 内置 内置 外置(tsc) 外置(tsc) 外置(tsc)
Tree-Shaking
Bundle大小 N/A N/A
适用场景 库开发 大型项目 库/工具 Web应用 Next.js
生产就绪 ★★★★★ ★★★★★ ★★★★★ ★★★★★ ★★★★
运行时优化 类型守卫优化 Branded Types const assertions 生成器 分层校验
性能提升 ★★★★ ★★★ ★★ ★★★ ★★★★
代码侵入
类型安全 ★★★ ★★★★★ ★★★★★ ★★★ ★★★★
学习成本
适用场景 热路径 ID类型 常量配置 大数据集 API边界

TypeScript性能优化不是"锦上添花",而是生产项目的"生命线"。 编译时用Project References和增量编译提速10倍,构建时用tsup/esbuild替代tsc获得tree-shaking,运行时避免热路径类型守卫,内存用Branded Types和const assertions减少膨胀,生产用性能预算和类型覆盖率守住底线。性能不是优化出来的,是设计出来的——从项目第一天就配好Project References,从第一个模块就用显式导出,从第一个类型就用const assertion。


推荐工具

  • JSON格式化 — 格式化tsconfig和构建产物JSON,快速排查配置问题
  • 代码格式化 — 格式化TypeScript代码,统一团队代码风格
  • cURL转代码 — 将API请求转为类型安全的TypeScript fetch代码

本站提供浏览器本地工具,免注册即可试用 →

#TypeScript性能#类型优化#编译加速#运行时优化#TypeScript 5.5#2026#前端工程