Type Annotations и Type Inference
Type Annotations — явное указание типа через
: Type, Type Inference — автоматическое выведение типа компилятором TypeScript из контекста; оба механизма работают вместе, позволяя писать типобезопасный код с минимальным количеством аннотаций.
Зачем нужно
Понимание того, когда TypeScript выводит тип сам, а когда нужна явная аннотация, — ключевой навык. Лишние аннотации замедляют написание кода и вносят шум; отсутствие нужных приводит к типу any или неожиданному расширению типа.
Где используется
- Аннотации параметров функций (обязательны — inference здесь не работает)
- Аннотации возвращаемого типа (опционально, но полезно для документации)
- Аннотации переменных для уточнения или сужения вывода
- Inference в стрелочных функциях, деструктурировании, generic вызовах
Основной контент
Type Annotation — явное указание типа
// Переменные
let name: string = "Alice";
let age: number = 30;
let active: boolean = true;
// Параметры функций — ВСЕГДА нужны
function greet(name: string, age: number): string {
return `Hello, ${name}! Age: ${age}`;
}
// Объекты
const user: { id: string; name: string } = { id: "u-1", name: "Bob" };
// Массивы
const nums: number = [1, 2, 3];
const items: Array<string> = ["a", "b"];
Type Inference — автовывод типа
// TypeScript выводит типы самостоятельно
let x = 42; // number
let s = "hello"; // string
let b = true; // boolean
let arr = [1, 2, 3]; // number
// Вывод из инициализатора функции
const double = (n: number) => n * 2; // (n: number) => number
// Вывод из return
function add(a: number, b: number) {
return a + b; // TypeScript знает что результат number
}
// Тип: (a: number, b: number) => number
// Контекстный вывод (contextual typing)
const nums = [1, 2, 3];
nums.map(n => n * 2); // n выводится как number из контекста
Когда аннотация нужна
// 1. Параметры функций — всегда
function process(data) { /* ... */ } // Error с noImplicitAny!
function process(data: unknown) { /* ... */ } // OK
// 2. Переменные без инициализатора
let result: string; // нет значения — тип нужен
result = "done";
// 3. Когда вывод шире чем нужно
const direction = "north"; // "north" (literal) — OK благодаря const
let dir = "north"; // string — нужен тип или as const
// 4. Возвращаемый тип для документации и защиты
function createUser(name: string): User {
// без аннотации TypeScript выведет, но явный тип — документация API
return { id: "1", name };
}
Widening и narrowing в inference
// let — тип «расширяется» (widening)
let msg = "hello"; // string, не "hello"
// const — тип сужается до литерала (no widening)
const greeting = "hello"; // "hello"
// Явный тип предотвращает widening
let direction: "north" | "south" = "north"; // literal type
// as const — принудительное сужение
const point = { x: 0, y: 0 } as const; // { readonly x: 0; readonly y: 0 }
Inference в generic функциях
function identity<T>(x: T): T { return x; }
const n = identity(42); // T выведен как number
const s = identity("hello"); // T выведен как string
// Явный generic нужен когда inference недостаточен
const arr = identity<string>(); // пустой массив → явный тип нужен
Частые ошибки
- Избыточные аннотации —
const x: number = 42;— лишняя аннотация, TypeScript и так выведетnumber. - Отсутствие аннотации параметра — TypeScript с
noImplicitAny: trueвыдаст ошибку; параметры всегда нужно аннотировать. - Полагаться на widened inference для
let—let direction = "north"имеет типstring, не"north"; нуженas constили явный literal type. - Не аннотировать возвращаемый тип public API — без явного return type изменение реализации может незаметно изменить контракт.
Альтернативная позиция: типы только там, где нет литерала
"Вы ставите типы только в тех местах, где у вас нет литерала или сигнатуры. Если мы пишем
3 + 4, это намберы — мне не надо прописывать сигнатуру."
Сравнение с Go: тип почти не указывается, выводится из литералов и возврата функций. У TS вывод типов слабее, потому что:
"У TypeScript очень плохой вывод типов. Он не может реализовать алгоритм Hindley-Milner."
Причина — особенности JS. Конструктор может вернуть что угодно:
class A {
constructor(s: string) { return s as any; }
}
const x = new A('hello'); // в JS x === 'hello' (string)
// TS не может это типизировать корректно
Поэтому местами явная аннотация в TS обязательна, даже там где другие языки бы вывели сами.
Связанные темы
- any, unknown -- различия
- Literal Types
- as const и const assertions
- Generic функции
- TS vs JS -- философия Тимура
- _MOC TypeScript
Источники
- 🎓 TypeScript vs JavaScript: как лучше писать типы · 2025-11-08 · YouTube
- Тезисы: типы только без литералов/сигнатуры; TS не реализует Hindley-Milner; вывод типов хуже, чем в Go.