Перейти к основному содержимому

Руководство по миграции на TypeScript

Перевод JavaScript-проекта на TypeScript облегчает сопровождение кода и делает работу в редакторе гораздо удобнее.

Это руководство шаг за шагом проведёт вас через миграцию.


Этап подготовки

Оцените свою кодовую базу. Прежде чем начинать миграцию:

  • Определите размер и сложность кодовой базы
  • Опишите процесс сборки и зависимости
  • Поищите уже имеющиеся описания типов (файлы .d.ts)
  • Найдите критически важные участки, которые требуют особого внимания

Настройте систему контроля версий. Убедитесь, что у вас чистый git-репозиторий (или его аналог). Создайте ветку для миграции и зафиксируйте текущее состояние, чтобы всегда можно было сравнить или откатиться:

  • git checkout -b typescript-migration
  • git add .
  • git commit -m "Pre-TypeScript migration state"

Настройка

Установите TypeScript как зависимость для разработки вместе с описаниями типов для Node.js: npm install --save-dev typescript @types/node

Создайте tsconfig.json. Начните с базовой конфигурации в корне проекта:

  • "compilerOptions": {
  • "target": "ES2020",
  • "module": "commonjs",
  • "strict": true,
  • "esModuleInterop": true,
  • "skipLibCheck": true,
  • "forceConsistentCasingInFileNames": true,
  • "outDir": "./dist",
  • "rootDir": "./src"
  • },
  • "include": ["src/**/*"],
  • "exclude": ["node_modules"]

Примечание: задайте в target самое старое окружение, которое вам нужно поддерживать.


Подходы к миграции

Есть два способа выполнить миграцию.

Постепенная миграция. Переводите по одному файлу за раз, пока остальной код остаётся на JavaScript. Включите "allowJs": true и "checkJs": true в compilerOptions, чтобы компилятор принимал и проверял файлы .js рядом с файлами .ts. Лучший вариант для больших кодовых баз, где важно минимально нарушать работу.

Миграция целиком. Переименуйте все файлы .js в .ts и исправьте ошибки, например с помощью find src -name "*.js" -exec sh -c 'mv "$0" "${0%.js}.ts"' {} \;. Лучший вариант для небольших и средних проектов, а также для новых проектов (с нуля).

Примечание: для больших проектов настоятельно рекомендуется постепенный подход — он меньше нарушает работу и делает объём задач посильным.


Пошаговая миграция

1. Начните с настройки. Создайте tsconfig.json с такими рекомендуемыми параметрами — они позволяют TypeScript проверять ваш JavaScript, пока ещё ничего не генерируя:

  • "compilerOptions": {
  • "target": "ES2020",
  • "module": "commonjs",
  • "strict": true,
  • "esModuleInterop": true,
  • "skipLibCheck": true,
  • "forceConsistentCasingInFileNames": true,
  • "outDir": "./dist",
  • "rootDir": "./src",
  • "allowJs": true,
  • "checkJs": true,
  • "noEmit": true
  • },
  • "include": ["src/**/*"],
  • "exclude": ["node_modules", "dist"]

2. Включите проверку типов для JavaScript. Добавьте // @ts-check в начало JavaScript-файла — и TypeScript начнёт его проверять (см. первый фрагмент кода). Отключить проверку одной строки можно с помощью // @ts-ignore.

3. Переименуйте файлы в .ts. Начните с некритичных файлов. Один файл переименовывается командой mv src/utils/helpers.js src/utils/helpers.ts, а целый каталог (осторожно) — командой find src/utils -name "*.js" -exec sh -c 'mv "$0" "${0%.js}.ts"' {} \;.

4. Добавьте аннотации типов. Постепенно задавайте типы параметров, возвращаемых значений и объектов (см. второй фрагмент кода).

5. Обновите скрипты сборки и тестирования. Добавьте компиляцию TypeScript в package.json: "scripts": { "build": "tsc", "dev": "tsc --watch", "test": "jest" }. Убедитесь, что ваша система тестирования тоже умеет запускать файлы TypeScript.

Проверка типов в JavaScript-файле

С // @ts-check и типом из JSDoc присваивание числа переменной name будет отмечено как ошибка.

script.ts
// @ts-check

/** @type {string} */
const name = 'John';

// TypeScript will catch this error
name = 42; // Error: Type '42' is not assignable to type 'string'

Добавление аннотаций типов

Одна и та же функция add до и после указания типов, а также интерфейс, использованный как тип возвращаемого значения.

script.ts
// Before
function add(a, b) {
  return a + b;
}

// After
function add(a: number, b: number): number {
  return a + b;
}

// With interface
interface User {
  id: number;
  name: string;
  email?: string;
}

function getUser(id: number): User {
  return { id, name: 'John Doe' };
}

Инструменты для миграции

Часть работы за вас могут сделать специальные инструменты:

  • ts-migrate — инструмент автоматической миграции с JavaScript на TypeScript: npx ts-migrate-full . (репозиторий на GitHub)
  • TypeStat — преобразует JavaScript в TypeScript с соблюдением типобезопасности: npx typestat (репозиторий на GitHub)
  • пакеты @types — описания типов для ваших зависимостей, например npm install --save-dev @types/react @types/node (искать их можно на TypeSearch)

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

Начинайте с малого и двигайтесь итерациями

  • Начните со вспомогательных функций и компонентов, не связанных с интерфейсом
  • Переводите по одному файлу или модулю за раз
  • Делайте коммит после каждого удачного шага

Используйте возможности TypeScript — пусть вывод типов работает за вас там, где это возможно, применяйте объединения типов для гибкости и защитники типов для проверок во время выполнения (см. фрагмент кода).

Работа со сторонними библиотеками

  • Устанавливайте пакеты @types для своих зависимостей
  • Пишите файлы объявлений для библиотек, у которых нет типов
  • Используйте declare module для расширения глобальных типов

Полезные возможности TypeScript

Вывод типов, псевдоним объединения типов и функция-защитник типа — три возможности, которые небольшими усилиями делают перенесённый код безопаснее.

script.ts
// Use type inference where possible
const name = 'John';  // TypeScript infers 'string'
const age = 30;       // TypeScript infers 'number'

// Use union types for flexibility
type Status = 'active' | 'inactive' | 'pending';

// Use type guards for runtime checks
function isString(value: any): value is string {
  return typeof value === 'string';
}

Типичные трудности и их решения

Динамические свойства

Проблема: в JavaScript обычные объекты часто используют как словари, добавляя свойства позже. TypeScript сообщает, что такого свойства не существует.

Решение: используйте индексную сигнатуру или утверждение типа.

Работа с контекстом this

Проблема: внутри обычной функции обратного вызова this теряется.

Решение: используйте стрелочную функцию или явно привяжите this.

Динамические свойства: проблема

Добавление name к пустому литералу объекта — это ошибка, потому что у его типа нет такого свойства.

script.ts
// Before
const user = {};
user.name = 'John';  // Error: Property 'name' does not exist

Динамические свойства: решение

Добавить свойство позволяет либо интерфейс с индексной сигнатурой, либо утверждение типа.

script.ts
// Option 1: Index signature
interface User {
  [key: string]: any;
}
const user: User = {};
user.name = 'John';  // OK

// Option 2: Type assertion
const user = {} as { name: string };
user.name = 'John';  // OK

Контекст this: проблема

Внутри обычной функции function, переданной в setTimeout, this больше не является экземпляром Counter.

script.ts
class Counter {
  count = 0;
  increment() {
    setTimeout(function() {
      this.count++;  // Error: 'this' is not defined
    }, 1000);
  }
}

Контекст this: решение

Стрелочная функция сохраняет окружающий this; как вариант, можно указать тип this как Counter и вызвать .bind(this).

script.ts
// Solution 1: Arrow function
setTimeout(() => {
  this.count++;  // 'this' is lexically scoped
}, 1000);

// Solution 2: Bind 'this'
setTimeout(function(this: Counter) {
  this.count++;
}.bind(this), 1000);

Заключение

Переход с JavaScript на TypeScript — это крупное, но окупающееся вложение в вашу кодовую базу. С этим руководством вы сможете сделать переход плавно и постепенно.

Главное:

  • Начните с продуманного tsconfig.json
  • Используйте allowJs и checkJs для постепенной миграции
  • Пусть система типов находит ошибки на раннем этапе
  • Обновите процесс сборки и тестирования, чтобы он поддерживал TypeScript
  • Решайте типичные трудности с помощью показанных выше приёмов

Миграция — это процесс, а не разовое событие. Смешанная кодовая база на время перехода — это нормально; важно ровное движение вперёд при сохранении качества кода.

Готовы начать? Настройте TypeScript в своём проекте и постепенно добавляйте аннотации типов — компилятор сам подскажет путь к более надёжному коду. Подробнее см. официальное руководство по миграции на TypeScript.