Хотите заказать веб-сайт? Связаться с нами

TypeScript: повторение ключевых тем курса

За двадцать уроков мы прошли путь от первых аннотаций типов до generics и тонкой настройки компилятора. В этом уроке нет новой теории — вместо этого мы соберём всё пройденное в единую картину, чтобы перед практикой с реальным проектом ничего не потерялось. Такой обзор особенно полезен, если какие-то темы шли плотно друг за другом и детали могли стереться из памяти.

  1. Синтаксис TypeScript: аннотации, массивы, объекты и unions
  2. Инструменты: TSX, TSC и настройка tsconfig.json
  3. Продвинутые типы: интерфейсы, generics и утилитные типы
  4. Работа с ошибками компилятора и типом any
TypeScript: повторение ключевых тем курса

Аннотации типов и строгая типизация

Всё началось с аннотаций типов — способа явно указать TypeScript, какого типа переменная, параметр функции или возвращаемое значение. В отличие от JavaScript, где тип значения определяется только во время выполнения, TypeScript проверяет соответствие типов ещё на этапе написания кода. Мы разбирали примитивные типы вроде string, number и boolean, а также литеральные типы, которые сужают допустимые значения до конкретного набора.

function add(a: number, b: number): number {
  return a + b;
}
 
let username: string = "Anjana";

Массивы, объекты и union-тип

Дальше мы научились типизировать более сложные структуры данных. Тип массива задаётся с помощью квадратных скобок после базового типа, а тип объекта описывается фигурными скобками с перечислением свойств и их типов. Отдельное внимание уделили необязательным свойствам объекта, которые помечаются знаком вопроса и допускают значение undefined.

Следом появились union-типы — способ объединить несколько типов в один с помощью символа |. Они пригодились там, где значение может быть, например, либо строкой, либо числом, а TypeScript должен принимать оба варианта как допустимые.

interface User {
  id: number;
  name: string;
  email?: string;
}
 
const tags: string[] = ["typescript", "types"];
let id: string | number;

Сужение типов, guards и алиасы

Union-тип полезен, но перед обращением к специфичным свойствам значения TypeScript требует его сузить — иначе компилятор не даст, например, вызвать метод строки на значении, которое может оказаться числом. Для этого мы использовали проверки вроде typeof и in, а также писали собственные type guards для более сложных случаев. Тип-алиасы, объявляемые через type, позволили давать длинным описаниям типов короткие имена и переиспользовать их в разных частях проекта.

Путь от JavaScript к TypeScript

Отдельный блок уроков был посвящён практической миграции. Мы переименовывали файлы с расширения .js на .ts и использовали утилиту TSX, чтобы запускать TypeScript-файлы напрямую, без предварительной компиляции. На практике мы добавляли аннотации к переменным и функциям, обрабатывали возможные undefined-значения и опирались на встроенный вывод типов там, где TypeScript мог определить тип сам.

Не переименовывайте сразу все файлы проекта в .ts. Начните с одного модуля, запустите проверку через tsc --noEmit, исправьте появившиеся ошибки и только потом переходите к следующему файлу — так миграция останется управляемой и предсказуемой.

TSX и TSC два инструмента компиляции

Мы подробно разобрали разницу между двумя утилитами. TSX нужен для быстрого запуска и итерации по коду без строгой проверки типов, а TSC — это полноценный компилятор TypeScript, который проверяет типы и превращает TypeScript-код в обычный JavaScript.

Инструмент Назначение
TSX Быстрый запуск .ts-файлов без строгой проверки типов
TSC Проверка типов и компиляция TypeScript в JavaScript

Мы также разобрали, что вся дополнительная синтаксическая информация TypeScript существует только на этапе компиляции и полностью исчезает в итоговом JavaScript-файле. Флаги вроде --noEmit, --checkJs и --strict позволяют управлять тем, что именно компилятор проверяет и какие файлы создаёт на выходе.

Настройка проекта через tsconfig.json

Чтобы не указывать одни и те же флаги вручную при каждом запуске, мы перенесли настройки компилятора в файл tsconfig.json. Там же мы задавали целевую версию JavaScript с помощью опции target, ведь разные версии среды выполнения поддерживают разный синтаксис. Отдельно рассмотрели базовые конфигурации — готовые наборы настроек для конкретных сред вроде Node.js или React, которые можно подключить и расширить вместо ручной настройки с нуля.

Интерфейсы и переиспользование типов

Интерфейсы стали основным способом описывать форму объектов в проекте. В отличие от простых объектных типов, интерфейсы можно расширять с помощью extends, наращивая более специфичные типы поверх общих. Это особенно удобно, когда несколько сущностей в приложении имеют общий набор базовых свойств.

interface Entity {
  id: number;
}
 
interface Event extends Entity {
  title: string;
  host: string;
}

Промисы и generics

С асинхронным кодом мы научились явно указывать, значение какого типа вернёт промис, используя угловые скобки после Promise. А чтобы не дублировать похожие типы для разных случаев, мы освоили generics — типы, принимающие параметр типа в угловых скобках и подставляющие его позже, в момент использования.

async function loadUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}
 
function identity<T>(value: T): T {
  return value;
}

Утилитные типы Partial Pick и Omit

На основе одного базового типа TypeScript позволяет строить производные с помощью встроенных утилитных типов. Partial делает все свойства типа необязательными, а Pick и Omit помогают собрать более узкий тип, выбрав или, наоборот, исключив конкретные свойства.

type UserPreview = Pick<User, "id" | "name">;
type UserWithoutEmail = Omit<User, "email">;
type PartialUser = Partial<User>;
  1. Partial делает все свойства типа необязательными
  2. Pick выбирает нужные свойства из типа
  3. Omit исключает указанные свойства из типа

Когда строгая типизация мешает

Иногда TypeScript не может корректно вывести тип, а переписывать логику ради компилятора нет времени. Для таких редких случаев существует тип any, отключающий проверку типов для конкретного значения, и комментарий // @ts-ignore, который заставляет компилятор пропустить ошибку на следующей строке. Оба инструмента стоит использовать точечно и осознанно, а не как способ обойти типизацию по умолчанию.

Если пришлось поставить // @ts-ignore, обязательно оставляйте рядом комментарий с причиной и задачей на исправление — иначе через месяц никто не вспомнит, почему проверка типов была отключена именно в этом месте.

Итоги

Мы прошли путь от простых аннотаций типов до интерфейсов, generics и утилитных типов, а заодно разобрались с инструментами TSX и TSC и настройкой проекта через tsconfig.json. Эта база пригодится в следующей части курса, где мы начнём применять TypeScript на реальном проекте Event Me.

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

Теги: