Narrowing & Type Guards
Сужение типов — ежедневный инструмент. На интервью гоняют по всем разновидностям guards: typeof, instanceof, in, equality, custom x is T, asserts. Красный флаг — не знать, что typeof null === ‘object’ и что Array.isArray — встроенный type guard.
Поток сужения для союза:
value: string | number | Date | null │ ▼ value === null ? ──── yes ──▶ null │ no ▼ typeof value === ‘string’ ? ──▶ string (.toUpperCase()) │ no ▼ typeof value === ‘number’ ? ──▶ number (.toFixed()) │ no ▼ value instanceof Date ? ──▶ Date (.getTime()) │ no ▼ never (exhaustive)
type Animal = { kind: 'cat'; meow(): void } | { kind: 'dog'; bark(): void };
function speak(a: Animal) { if ('meow' in a) a.meow(); // narrowing через in else a.bark();}
// custom type guardfunction isString(x: unknown): x is string { return typeof x === 'string';}
// asserts: бросает или гарантирует тип после вызоваfunction assert(cond: unknown, msg = 'assert'): asserts cond { if (!cond) throw new Error(msg);}
function head<T>(xs: T[]): T { assert(xs.length > 0, 'empty'); return xs[0]; // T, без undefined}Discriminated union сужается по литеральному дискриминатору — самый надёжный способ:
type Result = | { ok: true; value: number } | { ok: false; error: string };
function unwrap(r: Result): number { if (r.ok) return r.value; // здесь Result сужен до { ok: true; value: number } throw new Error(r.error);}Ловушки сужения: narrowing не сохраняется через async-замыкания для изменяемых переменных, instanceof не работает с интерфейсами (только с классами), а user-defined predicate x is T — TS слепо доверяет реализации.
// Ловушка: narrowing не работает через замыкание с mutable variablelet value: string | null = Math.random() > 0.5 ? "hello" : null;
if (value !== null) { // value: string здесь ✅ setTimeout(() => { // value может быть изменён снаружи — TS 4.4+ пытается сохранить narrowing, // но только для const-захваченных переменных console.log(value?.toUpperCase()); // безопаснее с ?. }, 0);}
// Правильный паттерн: захватить суженную копиюfunction safeAsync(x: string | null) { if (x === null) return; const safe = x; // const — narrowing сохраняется setTimeout(() => console.log(safe.toUpperCase()), 0); // ✅}
// instanceof не работает с интерфейсамиinterface Printable { print(): void }// if (x instanceof Printable) { } // ❌ компиляция: Printable — только тип
// User predicate — TS доверяет без проверки реализацииfunction isBigInt(x: unknown): x is bigint { return typeof x === "number"; // ОШИБКА в реализации, но TS не замечает!}Итог: Используй пять основных guards + custom x is T и asserts. Дискриминатор — самый надёжный способ.