Розширення (widening) - TypeScript виводить для змінної ширший тип, ніж конкретне значення, якщо змінна може змінитися.
const a = 'draft'; // тип 'draft' - константа ніколи не зміниться
let b = 'draft'; // тип string - змінна може отримати інше значення
Об'єкти й масиви розширюються навіть у const, бо їх вміст змінюваний:
const post = { status: 'draft' }; // { status: string }
post.status = 'anything'; // дозволено
function publish(status: 'draft' | 'published') {}
publish(post.status); // помилка: string не 'draft' | 'published'
Способи зберегти вузький тип:
const post = { status: 'draft' as const }; // { status: 'draft' }
const post2 = { status: 'draft' } as const; // { readonly status: 'draft' }
const post3: { status: 'draft' | 'published' } = { status: 'draft' };
const post4 = { status: 'draft' } satisfies { status: 'draft' | 'published' };
as const- найвужчі літеральні типи йreadonlyна всіх рівнях;- анотація - тип визначає анотація (вона ж і обмежує);
satisfies- перевіряє відповідність, але зберігає виведений тип значення.
Контекстна типізація - виведення «ззовні»: якщо тип очікуваного значення відомий, літерал не розширюється:
type Options = { method: 'GET' | 'POST' };
const options: Options = { method: 'GET' }; // ок
fetchJson({ method: 'GET' }); // ок, якщо параметр типізовано як Options
Також параметри колбеків отримують типи з контексту: items.map((item) => ...).
«Найкращий загальний тип» для масивів - об'єднання типів елементів: [1, 'a'] - (string | number)[], а не кортеж.
Константні параметри типу (TS 5.0) - function f<const T>(x: T) змушує виводити T так, ніби аргумент записано з as const, без вимоги до викликача.
Типова помилка в коді:
const config = { mode: 'production' }; // mode: string
createApp(config); // помилка: очікується 'production' | 'development'
Рішення - satisfies AppConfig чи анотація типу при оголошенні, а не as при виклику: as вимикає перевірку значення.
Звуження після перевірок - зворотний процес: let x: string | number, а після typeof x === 'string' у гілці - string.