Система типов
Что это такое
TypeScript описывает форму значения: какие у него свойства, что в них лежит, что функция принимает и что возвращает. Описания сравнивает компилятор и там, где они расходятся, сообщает об этом. Система структурная: два типа совместимы, если у них одни и те же члены, независимо от названий.
Почему это важно
Насколько система типов полезна, зависит от того, сколько она выводит сама, без подсказок. Кодовая база, где почти всё приходится аннотировать руками, — это вторая копия программы, которую тоже надо поддерживать, и со временем она расходится с оригиналом. Если же большинство типов выведено, остаётся несколько небольших объявлений на границах — параметры функций, экспортируемые формы, — а всё внутри проверяется от них. Где проходят эти границы — вот в чём весь навык, и от этого зависит, будет TypeScript полезным инструментом или лишней работой без выгоды.
Ключевые идеи
Типы структурные, а не номинальные
Объект совместим с типом, если у него есть все требуемые члены совместимых типов, а объявлять, что класс реализует интерфейс, не нужно: значение этого класса удовлетворяет ему и так. Два типа с одинаковой формой, объявленные порознь, взаимозаменяемы.
type Point = { x: number; y: number };
const origin = { x: 0, y: 0, label: "O" };
const p: Point = origin; // нормально — лишние свойства здесь допустимы
Исключение — проверка лишних свойств: когда объектный литерал присваивают прямо типизированной переменной, неизвестные свойства действительно отмечаются, и происходит это именно потому, что опечатка обычно случается как раз там.
Пусть вывод делает работу
Параметры аннотируйте тогда, когда их нельзя вывести, а возвращаемые типы экспортируемых функций — чтобы у вызывающего кода был устойчивый контракт; внутри функции пусть компилятор сам разберётся, какого типа локальная переменная. Неверная аннотация там — ошибка, а не документация.
function totalCents(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.priceCents * item.quantity, 0);
}
Для объединений берите type, для расширения — interface
Оба описывают форму объекта. interface можно открыть заново и слить с другим объявлением — для дополнения библиотек это удобно, во всех остальных случаях опасно; type же составляет объединения и пересечения, слиянию не поддаётся, поэтому для прикладного кода это более безопасный выбор по умолчанию.
Моделируйте отсутствие явно
strict: true делает null и undefined обычными типами, а не чем-то, что прячется за каждым значением. Один этот флаг превращает «cannot read property of undefined» из падения во время выполнения в ошибку компиляции, поэтому включать его стоит первым.
type User = { id: string; name: string; deletedAt: string | null };
any отключает проверку для значения
any заразен для всего, чего касается, а его безопасный аналог unknown принимает что угодно, но требует проверки перед использованием. Там, где границу действительно нельзя типизировать — сторонний скрипт, полезная нагрузка JSON, — предпочитайте unknown плюс функцию сужения на краю.
Практика
Типизируйте границу, остальное выводите
Возьмите небольшой модуль на JavaScript, который получает данные и отрисовывает их. Аннотируйте только возвращаемый тип функции загрузки и параметр функции отрисовки, затем включите strict, запустите компилятор и исправьте то, что он сообщит, — больше нигде аннотаций не добавляйте.
Готово, когда: модуль компилируется со strict, ни одна локальная переменная не несёт явной аннотации, и компилятор ловит намеренную опечатку, которую вы вносите в отрисовку.
Замените три any настоящим типом
Найдите в кодовой базе три употребления any, выпишите форму, которая на самом деле есть у каждого значения, и замените их типом или unknown.
Готово, когда: ни одно из трёх не компилируется без проверки, доказывающей форму, и хотя бы один скрытый дефект выходит наружу.
Частые ошибки
- Аннотировать всё подряд — аннотации превращаются во вторую программу, которая расходится с первой; исправить: аннотируйте параметры и экспортируемые возвраты, а локальные переменные оставьте выводу.
- Оставлять
strictвыключенным —nullпроскакивает мимо всех проверок, и ошибки во время выполнения остаются; исправить: включитеstrictи исправляйте найденные ошибки по одному файлу за раз. - Гасить ошибку через
any— проверка перестаёт помогать для этого значения и всего производного от него; исправить: используйтеunknownи сужайте либо опишите настоящую форму. - Тянуться к
as, чтобы подогнать тип — приведение отменяет решение компилятора, не меняя значение во время выполнения; исправить: сделайте значение действительно подходящим или проверьте его на границе. - Типизировать функцию по её реализации — тогда вызывающий код зависит от внутренних деталей, и каждый рефакторинг его ломает; исправить: описывайте, что функция обещает, а не как она это делает сейчас.
Что читать дальше
- JavaScript (02-frontend/06-javascript) — семантика значений, которые вы описываете; типы не меняют ни поведение
this, ни приведение типов. - Сужение типов и объединения (02-frontend/19-typescript/02-narrowing-and-unions) — там, где система типов начинает предотвращать состояния, а не только описывать их.
- Чистый код (01-cs-fundamentals/08-clean-code) — ясная сигнатура преследует ту же цель, но достигает её без компилятора.