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 для letlet 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 обязательна, даже там где другие языки бы вывели сами.

Связанные темы

Источники

  • 🎓 TypeScript vs JavaScript: как лучше писать типы · 2025-11-08 · YouTube
    • Тезисы: типы только без литералов/сигнатуры; TS не реализует Hindley-Milner; вывод типов хуже, чем в Go.

Ресурсы