Упродовж понад десятиліття однією з найбільш дратівливих проблем типізації в TypeScript була нездатність компілятора автоматично звузити тип масиву після виклику стандартного методу Array.prototype.filter():
// Legacy TypeScript (версії нижче 5.5):
const numbersOrNull = [1, 2, null, 4, null];
const onlyNumbers = numbersOrNull.filter((x) => x !== null);
// ТИП ВИВОДИВСЯ ЯК: (number | null)[]
// Компілятор не розумів, що всі значення null були гарантовано відфільтровані!Щоб виключити null або undefined із результуючого масиву, розробники були змушені писати ручні функції з предикатами типів (Type Predicate):
// Вимушений boilerplate до TypeScript 5.5:
const onlyNumbers = numbersOrNull.filter(
(x): x is number => x !== null
);Цей підхід породжував небезпечний клас архітектурних дефектів — Type Guard Lie. Якщо програміст припускався помилки в предикаті або тілі функції, компілятор сліпо довіряв сигнатурі x is number, маскуючи критичні збої Cannot read property of undefined.
Починаючи з TypeScript 5.5+, компілятор навчився виконувати автоматичний вивід предикатів типів (Inferred Type Predicates) під час аналізу потоку керування (Control Flow Analysis / CFA).
TypeScript 5.5+ автоматично виводить сигнатуру (x: T): x is NarrowedType для звичайних і стрілкових функцій, які повертають булеву перевірку типу, назавжди усуваючи потребу в ручному анотуванні у filter() та захищаючи від помилок Type Guard Lie.
У TypeScript 5.5+ компілятор аналізує абстрактне синтаксичне дерево (AST) тіла функції та зіставляє вхідні аргументи з поверненим булевим виразом.
Якщо функція задовольняє чотири строгі інваріанти:
: boolean, компілятор примусово вимкне вивід предиката і поверне примітивний логічний тип;return condition або всі гілки однозначно повертають булеві результати;typeof x === 'string', x !== null, x instanceof Date, перевірка дискримінатора x.kind === 'admin');компілятор автоматично синтезує тип повернення parameter is NarrowedType замість банального boolean.
+-------------------------------------------------------------------------+
| Еволюція виводу предикатів у TypeScript 5.5+ |
+-------------------------------------------------------------------------+
| До TypeScript 5.5: |
| const isString = (x: unknown) => typeof x === 'string'; |
| • Сигнатура функції: (x: unknown) => boolean |
| • items.filter(isString) => тип залишається unknown[] |
| • Було потрібно: (x: unknown): x is string => ... (крихкий boilerplate) |
+-------------------------------------------------------------------------+
│
▼ Реліз TypeScript 5.5+
+-------------------------------------------------------------------------+
| Сучасний TypeScript 5.5+: |
| const isString = (x: unknown) => typeof x === 'string'; |
| • Сигнатура компілятора: (x: unknown): x is string |
| • items.filter(isString) => тип автоматично звужується до string[] |
| • Нульовий boilerplate, математична гарантія відповідності тіла функції|
+-------------------------------------------------------------------------+Компілятор переконується, що у функції немає примусової анотації типу повернення : boolean.
Type Checker досліджує булевий вираз на наявність операцій звуження типу аргументу.
Аналізатор Control Flow Analysis вираховує підтип, який залишається від вихідного об'єднання при істинності виразу.
Функція неявно отримує предикатну сигнатуру, активуючи правильні перевантаження в Array.prototype.filter.
Розробник написав: 'const isNumber = (val: unknown): boolean => typeof val === 'number';'. Чому виклик [1, 'two', 3].filter(isNumber) у TypeScript 5.5 повертає тип (string | number)[], а не number[]?
Розділення успішних та відхилених асинхронних завдань: settled.filter(r => r.status === 'fulfilled'). Компілятор автоматично звужує масив до PromiseFulfilledResult<T>[], відкриваючи безпечний доступ до поля .value без ручного приведення типів as.
Під час вибірок із GraphQL або баз даних з опціональними зв'язками: users.filter(u => u.profile !== null). Результуючий список отримує тип гарантовано ініціалізованих профілів.
Фільтрація потоку подій: events.filter(e => e.type === 'ORDER_CREATED'). Компілятор миттєво розпізнає Discriminated Unions та звужує тип події.
Метод array.find(item => item instanceof AdminUser) у TypeScript 5.5+ автоматично повертає тип AdminUser | undefined замість широкого User | undefined.
Розглянемо конвеєр фільтрації різнорідних подій та агрегації паралельних завдань.
Вихідний масив містить об'єднання типів: (UserSession | null | undefined)[].
Розробник передає функцію-фільтр без анотації типу повернення: (s) => s != null.
Компілятор виводить сигнатуру: (s: UserSession | null | undefined): s is UserSession.
Сигнатура Array.prototype.filter обирає перевантаження S[] і повертає UserSession[].
export interface UserSession {
id: string;
email: string;
role: 'admin' | 'editor' | 'guest';
lastActive: Date | null;
}
/**
* 1. Фільтрація примітивів:
* Компілятор виводить: (val: string | number | null): val is string
*/
export const isString = (val: string | number | null) => typeof val === 'string';
const mixedValues: (string | number | null)[] = ['alpha', 42, null, 'beta'];
// У TypeScript 5.5+ stringsOnly має строгий тип string[]!
export const stringsOnly = mixedValues.filter(isString);
/**
* 2. Інлайн-фільтрація з оператором нерівності:
* Сигнатура автоматично виводиться як: (s: UserSession | null | undefined): s is UserSession
*/
export function getActiveSessions(
sessions: readonly (UserSession | null | undefined)[]
): UserSession[] {
// Тип автоматично звужується до UserSession[]
return sessions.filter((s) => s !== null && s !== undefined);
}
/**
* 3. Звуження розмічених об'єднань (Discriminated Unions)
*/
export type AppEvent =
| { type: 'AUTH_SUCCESS'; userId: string; token: string }
| { type: 'AUTH_FAILURE'; error: string }
| { type: 'HEARTBEAT'; timestamp: number };
/**
* Сигнатура автоматично інфериться як:
* (event: AppEvent): event is { type: 'AUTH_SUCCESS'; userId: string; token: string }
*/
export const isAuthSuccessEvent = (event: AppEvent) => event.type === 'AUTH_SUCCESS';
export function extractAuthTokens(events: readonly AppEvent[]): string[] {
// Компілятор знає, що у відфільтрованих подій гарантовано присутнє поле .token!
return events
.filter(isAuthSuccessEvent)
.map((e) => e.token);
}Чи можна автоматично вивести предикат типу для асинхронної функції 'async function checkAccess(user: User): Promise<boolean>'?
Демонстрація промислового агрегатора асинхронних завдань, що використовує автоматичний вивід предикатів для безпечного розділення результатів Promise.allSettled.
export interface TaskResult<T> {
successful: T[];
failedErrors: Error[];
}
/**
* Агрегатор паралельних завдань з автоматичним звуженням типів PromiseSettledResult
*/
export async function runParallelTasks<T>(
tasks: readonly Promise<T>[]
): Promise<TaskResult<T>> {
const settled = await Promise.allSettled(tasks);
// Предикат (r): r is PromiseFulfilledResult<T> виводиться АВТОМАТИЧНО!
const fulfilled = settled.filter(
(r) => r.status === 'fulfilled'
);
// Предикат (r): r is PromiseRejectedResult виводиться АВТОМАТИЧНО!
const rejected = settled.filter(
(r) => r.status === 'rejected'
);
return {
successful: fulfilled.map((r) => r.value),
failedErrors: rejected.map((r) =>
r.reason instanceof Error ? r.reason : new Error(String(r.reason))
),
};
}Найбільш поширена помилка розробників, які звикли явно типізувати повернені значення функцій:
Якщо написати const isValid = (x: unknown): boolean => x !== null, компілятор примусово вимкне вивід предиката. У результаті в array.filter(isValid) звуження типів не працюватиме!
// ❌ АНТИПАТЕРН: Явний : boolean пригнічує вивід предиката
const isNotNull = (x: string | null): boolean => x !== null;
const list = ['a', null, 'b'].filter(isNotNull); // Тип: (string | null)[]
// ✅ ПРАВИЛЬНО: Відсутність анотації активує автоматичний предикат x is string
const isNotNull = (x: string | null) => x !== null;
const list = ['a', null, 'b'].filter(isNotNull); // Тип: string[]Компілятор TypeScript 5.5 синтезує предикат лише тоді, коли зв'язок між аргументом і результатом прозорий. Якщо всередині функції вводяться проміжні змінні, відбуваються мутації або викликаються непрозорі сторонні методи, компілятор відкотиться до звичайного boolean. У таких випадках потрібен явний предикат x is Type.
Якщо перевірити return Boolean(user.details?.profile?.verified), компілятор зрозуміє, що поле verified істинне, але не зможе звузити кореневий об'єкт user, якщо user міг бути null. Завжди звужуйте батьківський контекст перед перевіркою дочірніх властивостей.
Поява Inferred Type Predicates у TypeScript 5.5+ — одне з найважливіших поліпшень мови за останні роки. Воно робить роботу зі стандартним методом Array.prototype.filter інтуїтивною та типобезпечною «з коробки», виключає написання крихкого коду x is Type та математично захищає кодову базу від фатальних розсинхронізацій типів (Type Guard Lie).