Что такое система типов и зачем она нужна
Проблема: JavaScript не защищает от ошибок
JavaScript — динамически типизированный язык. Переменная может быть чем угодно, и узнаёшь ты об ошибке только в рантайме:
function greet(user) {
return `Привет, ${user.name}!`
}
greet({ name: 'Алекс' }) // "Привет, Алекс!"
greet('Алекс') // "Привет, undefined!" — нет ошибки, но баг
greet(null) // TypeError: Cannot read properties of null
Последняя строка упадёт в продакшене. Первые две даже не предупредят. Это норма для JavaScript — ошибки типов проявляются только когда код выполняется.
Что такое система типов
Система типов — это набор правил, которые классифицируют значения и проверяют, что операции над ними имеют смысл.
Простая ментальная модель: тип — это множество возможных значений.
number— множество всех чиселstring— множество всех строк'hello'— множество из одного элемента (литеральный тип)never— пустое множество
TypeScript проверяет, принадлежит ли значение нужному множеству до запуска кода — на этапе компиляции.
function greet(user: { name: string }) {
return `Привет, ${user.name}!`
}
greet({ name: 'Алекс' }) // ✅ OK
greet('Алекс') // ❌ Ошибка компиляции: string не совместим с { name: string }
greet(null) // ❌ Ошибка компиляции: null не совместим с { name: string }
Ошибки ловятся до запуска — в редакторе, при сборке, в CI.
Статическая vs динамическая типизация
| Статическая (TypeScript) | Динамическая (JavaScript) | |
|---|---|---|
| Когда проверка | Compile time | Runtime |
| Где ловятся ошибки | В редакторе / CI | В продакшене |
| Скорость обратной связи | Мгновенная | После запуска |
| Стоимость ошибки | Красное подчёркивание | Баг у пользователя |
| Гибкость | Нужно описывать типы | Полная свобода |
Динамическая типизация быстрее для прототипов. Статическая — безопаснее для продакшена. TypeScript предлагает компромисс: постепенная типизация (gradual typing). Можно добавлять типы постепенно, а any позволяет обойти проверку там, где нужно.
Type erasure — типы исчезают
Ключевой принцип TypeScript: типы существуют только на этапе компиляции. При транспиляции в JavaScript все аннотации типов полностью удаляются:
// TypeScript
const name: string = 'Алекс'
const age: number = 25
function greet(user: { name: string; age: number }): string {
return `${user.name}, ${user.age}`
}
// Скомпилированный JavaScript — типов нет
const name = 'Алекс'
const age = 25
function greet(user) {
return `${user.name}, ${user.age}`
}
Это называется type erasure — стирание типов. Интерфейсы, type alias, generic-типы — всё исчезает. В рантайме работает обычный JavaScript, и движок V8 не знает, что код был написан на TypeScript.
Внимание: TypeScript НЕ добавляет runtime-проверки. Если данные приходят из API, пользовательского ввода или JSON.parse — типы не гарантируют корректность. Для рантайм-валидации используйте Zod, io-ts или class-validator.
Зачем нужны типы — практические причины
1. Автодополнение и навигация
Типы — это документация, которую понимает редактор. Вместо угадывания user.??? — точный список полей с типами:
interface User {
id: number
name: string
email: string
}
function sendEmail(user: User) {
user. // ← IDE покажет: id, name, email
}
2. Рефакторинг без страха
Переименовал поле — компилятор покажет все места, где оно используется. Изменил сигнатуру функции — все вызовы подсветятся ошибками.
3. Документация в коде
Типы описывают контракт функции лучше, чем JSDoc-комментарии. И, в отличие от комментариев, типы не устаревают — компилятор проверяет их при каждой сборке.
4. Ловля багов до продакшена
Исследования (Microsoft, Google) показывают, что статическая типизация предотвращает 15-20% багов, которые доходят до продакшена. Это не серебряная пуля, но значимый фильтр.
Почему TypeScript не является «абсолютно правильным»
TypeScript сознательно unsound — то есть не гарантирует, что если код скомпилировался, в рантайме не будет ошибки типа.
Это не баг, а дизайн-решение. Из официальных Design Goals:
Цель #9: «Использовать согласованную, полностью стираемую, структурную систему типов» Не-цель #3: «Применять звуковую (sound) или доказуемо корректную систему типов»
Причина — прагматизм. Абсолютно корректная система типов была бы слишком ограничивающей для реального JavaScript-кода.
Копай глубже: Soundness vs Completeness — формальный взгляд
В теории типов есть два свойства:
- Soundness (корректность): если программа прошла проверку типов, runtime-ошибок типа не будет
- Completeness (полнота): если программа корректна, проверка типов её пропустит
Ни одна практичная система типов не обладает обоими свойствами (следствие теоремы Гёделя). TypeScript жертвует soundness ради completeness и удобства. Примеры «дыр»: any, type assertions (as), ковариантные массивы, отсутствие проверки границ при индексном доступе.
Итого
| Факт | Описание |
|---|---|
| Что это | Набор правил для классификации значений и проверки операций |
| Ментальная модель | Тип = множество возможных значений |
| Когда проверка | Compile time, до запуска кода |
| Type erasure | Типы стираются при компиляции — в рантайме их нет |
| Unsoundness | TypeScript сознательно не гарантирует 100% безопасность типов |
| Главная ценность | Автодополнение, рефакторинг, документация, ловля багов |