Пошук уроків, статей та іншого контенту
Описуйте функції, які перевіряють умови й повідомляють TypeScript про гарантований тип після виклику.
Assertion function — це функція, яка перевіряє умову під час виконання програми та повідомляє TypeScript, що після її успішного завершення певне твердження є істинним.
Завдяки цьому TypeScript може звузити тип значення після виклику функції.
function assert(condition: unknown, message: string): asserts condition {
if (!condition) {
throw new Error(message);
}
}Сигнатура asserts condition означає:
якщо функція завершилася нормально, condition вважається істинною;
якщо умова хибна, функція має перервати виконання, зазвичай через throw;
після виклику TypeScript використовує цю інформацію для звуження типів.
asserts conditionРозглянемо приклад із nullable-значенням:
function assert(condition: unknown, message: string): asserts condition {
if (!condition) {
throw new Error(message);
}
}
function printUserName(name: string | null): void {
assert(name !== null, "Ім'я користувача відсутнє");
// Після assert TypeScript знає, що name має тип string
console.log(name.toUpperCase());
}
printUserName("Olena");До виклику assert змінна name має тип string | null.
Після перевірки:
assert(name !== null, "Ім'я користувача відсутнє");TypeScript виключає null із типу name, оскільки при null функція завершується помилкою.
Якщо передати null, перевірка відбудеться під час виконання:
printUserName(null);
// Error: Ім'я користувача відсутнєasserts value is TypeФорма asserts value is Type пов’язує assertion function із конкретним параметром.
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new TypeError("Очікувався рядок");
}
}
function printLength(value: unknown): void {
assertIsString(value);
// Після перевірки value має тип string
console.log(value.length);
}
printLength("TypeScript");До виклику assertIsString параметр value має тип unknown.
Після успішного виклику:
assertIsString(value);TypeScript знає, що value — це string, тому доступ до value.length є безпечним.
Таку функцію можна використовувати для перевірки даних, отриманих із джерел, тип яких невідомий:
type User = {
id: number;
name: string;
};
function assertIsUser(value: unknown): asserts value is User {
if (typeof value !== "object" || value === null) {
throw new TypeError("Значення не є об'єктом");
}
const user = value as Record<string, unknown>;
if (typeof user.id !== "number" || typeof user.name !== "string") {
throw new TypeError("Некоректна структура користувача");
}
}
const input: unknown = JSON.parse('{"id": 42, "name": "Iryna"}');
assertIsUser(input);
// Після assertion input має тип User
console.log(input.id);
console.log(input.name.toUpperCase());Assertion function перевіряє дані під час виконання, а її сигнатура дає TypeScript інформацію для статичної перевірки.
Assertion function не повертає результат перевірки як boolean. Вона або:
завершується нормально, підтверджуючи твердження;
генерує помилку.
Звичайний type predicate має інший вигляд:
function isString(value: unknown): value is string {
return typeof value === "string";
}
const value: unknown = "hello";
if (isString(value)) {
console.log(value.toUpperCase());
}Type predicate повертає boolean, тому його зазвичай використовують у if.
Assertion function використовується, коли невиконання умови має вважатися помилкою:
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new TypeError("Очікувався рядок");
}
}
const value: unknown = "hello";
assertIsString(value);
console.log(value.toUpperCase());Вибір залежить від логіки програми:
value is Type — коли невідповідність є звичайним результатом перевірки;
asserts value is Type — коли невідповідність має перервати виконання.
Assertion functions зручно створювати для загальних перевірок.
function assertDefined<T>(
value: T,
message = "Значення не визначене"
): asserts value is NonNullable<T> {
if (value === null || value === undefined) {
throw new Error(message);
}
}
function getFirstItem(items: string[]): string {
const firstItem: string | undefined = items[0];
assertDefined(firstItem, "Список порожній");
// firstItem має тип string
return firstItem;
}
console.log(getFirstItem(["TypeScript", "JavaScript"]));NonNullable<T> виключає з типу T значення null та undefined.
Наприклад:
NonNullable<string | null | undefined>
// stringЦе дає змогу написати одну універсальну функцію для перевірки обов’язкових значень.
Assertion functions також можуть звужувати об’єднані типи.
type Success = {
status: "success";
data: string;
};
type Failure = {
status: "failure";
error: string;
};
type Result = Success | Failure;
function assertSuccess(result: Result): asserts result is Success {
if (result.status !== "success") {
throw new Error(result.error);
}
}
function processResult(result: Result): string {
assertSuccess(result);
// result має тип Success
return result.data.toUpperCase();
}
console.log(processResult({
status: "success",
data: "completed"
}));До виклику assertSuccess змінна result може бути як Success, так і Failure.
Після успішної перевірки TypeScript знає, що залишився лише тип Success.
Assertion function повинна справді переривати виконання, якщо умова не виконується:
function assertPositive(value: number): asserts value is number {
if (value <= 0) {
throw new Error("Число має бути додатним");
}
}TypeScript довіряє сигнатурі assertion function. Він не перевіряє, чи відповідає реалізація заявленому твердженню.
Тому небезпечна реалізація може створити невідповідність між типами та фактичними значеннями:
function assertIsNumber(value: unknown): asserts value is number {
// Помилка: перевірки немає
}
const value: unknown = "not a number";
assertIsNumber(value);
// TypeScript вважає value числом,
// хоча фактично це рядок
console.log(value.toFixed(2));У цьому прикладі помилка виникне вже під час виконання. Отже, assertion function повинна містити реальну перевірку.
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
console.log("Неправильний тип");
}
}Такий код не перериває виконання. Для TypeScript функція все одно вважається успішною, тому він звузить тип до string.
Правильний варіант:
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new TypeError("Очікувався рядок");
}
}Не слід оголошувати, що значення має тип, якщо перевірено лише частину умов.
type User = {
id: number;
name: string;
};
function assertIsUser(value: unknown): asserts value is User {
if (typeof value === "object" && value !== null) {
// Цієї перевірки недостатньо для типу User
}
}Перевірка має підтверджувати всі властивості, необхідні для заявленого типу.
asserts condition і asserts value is TypeДля перевірки загальної умови використовуйте:
function assert(condition: unknown): asserts condition {
if (!condition) {
throw new Error("Умова не виконана");
}
}Для звуження конкретного значення до типу використовуйте:
function assertIsNumber(value: unknown): asserts value is number {
if (typeof value !== "number") {
throw new TypeError("Очікувалося число");
}
}Якщо хибний результат є нормальною ситуацією, краще повернути boolean:
function isNumber(value: unknown): value is number {
return typeof value === "number";
}
function formatValue(value: unknown): string {
if (isNumber(value)) {
return value.toFixed(2);
}
return "Значення не є числом";
}Assertion function доречна тоді, коли невиконання умови є помилкою програми або порушенням очікуваного контракту.
Assertion function перевіряє умову під час виконання та звужує типи для TypeScript.
asserts condition повідомляє, що передана умова істинна після успішного виклику.
asserts value is Type звужує конкретне значення до вказаного типу.
Assertion function має генерувати помилку, якщо твердження хибне.
TypeScript довіряє сигнатурі assertion function, тому реалізація повинна відповідати заявленому типу.
Для звичайного результату true або false краще використовувати type predicate value is Type.
Assertion functions особливо корисні під час перевірки значень типу unknown, nullable-даних і об’єднаних типів.