Оптимизация производительности TypeScript приложений: на практике

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему TypeScript может быть медленным?

TypeScript — мощный инструмент, который добавляет статическую типизацию в JavaScript. Однако разработчики часто сталкиваются с тем, что "из коробки" TypeScript-приложения работают медленнее, чем чистый JS. Основные причины — это избыточная транспиляция, неправильная настройка компилятора и неоптимальные паттерны кода. В этой статье мы разберем практические методы оптимизации, которые помогут ускорить как процесс сборки, так и рантайм вашего приложения.

1. Профилирование и поиск узких мест

Прежде чем что-то оптимизировать, нужно понять, что именно тормозит. Используйте встроенные инструменты браузера и Node.js.

1.1. Chrome DevTools Performance

Запишите профиль выполнения приложения. Обратите внимание на длительные задачи (Long Tasks) и частые вызовы сборщика мусора (GC). TypeScript-код после компиляции — это обычный JavaScript, поэтому все стандартные методы профилирования работают.

1.2. Анализ времени сборки (tsc --generateTrace)

Если медленная сама компиляция, используйте флаг --generateTrace:

tsc --generateTrace trace_output

Затем откройте файл trace_output/trace.json в Chrome по адресу chrome://tracing. Вы увидите, какие файлы компилируются дольше всего. Часто это большие библиотеки или файлы с избыточными импортами.

2. Настройка tsconfig.json для максимальной скорости

Неправильная конфигурация — главный враг производительности. Рассмотрим ключевые опции.

2.1. strict: true — не спешите

Режим strict включает все строгие проверки типов. Это замедляет компиляцию. Для продакшн-сборки используйте:

{
  "compilerOptions": {
    "strict": false,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}

Эти две опции покрывают 90% ошибок, но работают быстрее полного strict.

2.2. Опции для ускорения сборки

  • incremental: true — включает инкрементальную компиляцию. TypeScript сохраняет информацию о предыдущей сборке и перекомпилирует только измененные файлы.
  • skipLibCheck: true — отключает проверку типов в файлах деклараций (.d.ts). Это дает значительный прирост скорости, особенно если используется много сторонних библиотек.
  • isolatedModules: true — позволяет использовать более быстрые транспиляторы (например, esbuild) вместо tsc.

Пример оптимизированной конфигурации для разработки:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "incremental": true,
    "skipLibCheck": true,
    "strict": false,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.test.ts"]
}

3. Оптимизация рантайма: типы и структуры данных

TypeScript-типы исчезают после компиляции, но их "отпечаток" остается в виде кода, который генерирует компилятор.

3.1. Избегайте сложных условных типов в рантайме

Сложные conditional types могут генерировать много кода. Вместо этого используйте простые union-типы:

// Плохо: сложный условный тип
type ResponseType<T> = T extends string ? { data: string } : { data: number };
function handleResponse<T>(value: T): ResponseType<T> { ... }

// Хорошо: простой union
type ResponseType = { data: string } | { data: number };
function handleResponse(value: string | number): ResponseType { ... }

3.2. Используйте as const для констант

Это помогает TypeScript выводить более точные типы, что уменьшает количество проверок:

const COLORS = ['red', 'green', 'blue'] as const;
// Тип: readonly ["red", "green", "blue"]

// Вместо:
const COLORS = ['red', 'green', 'blue']; // Тип: string[]

3.3. Мемоизация дорогих вычислений

Если функция вызывается часто и возвращает одинаковый результат для одинаковых аргументов, используйте мемоизацию. Простой пример на TypeScript:

function memoize<T extends (...args: any[]) => any>(fn: T): T {
  const cache = new Map<string, ReturnType<T>>();
  return ((...args: Parameters<T>) => {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key)!;
    }
    const result = fn(...args);
    cache.set(key, result);
    return result;
  }) as T;
}

// Использование
const expensiveCalc = memoize((a: number, b: number) => {
  // Дорогой расчет
  return a + b;
});

4. Ленивая загрузка и динамические импорты

Разделяйте код на чанки (chunks) с помощью динамических импортов. Это уменьшает размер начального бандла.

4.1. Динамические импорты в TypeScript

TypeScript поддерживает динамические импорты "из коробки". Пример для React-приложения:

// Вместо статического импорта
// import HeavyComponent from './HeavyComponent';

// Используем динамический импорт
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Загрузка...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

4.2. Типизация lazy-компонентов

Чтобы TypeScript не ругался, укажите тип для lazy-компонента:

import { ComponentType, LazyExoticComponent } from 'react';

const HeavyComponent: LazyExoticComponent<ComponentType<{}>> = React.lazy(
  () => import('./HeavyComponent').then(module => ({ default: module.HeavyComponent }))
);

Заключение

Блоги

Рекомендаций по книгам