Пошук уроків, статей та іншого контенту
Як компілятор звужує широкий тип до конкретнішого всередині перевірок умов — і never для вичерпності.
Type narrowing — здатність компілятора TypeScript звужувати широкий тип (наприклад, union із попереднього уроку) до конкретнішого всередині конкретної гілки коду, спираючись на звичайні перевірки умов, які код і так робив би на JavaScript-рівні.
function double(value: string | number): string | number {
if (typeof value === "number") {
return value * 2; // тут TypeScript знає: value — саме number
}
return value.repeat(2); // тут — точно string
}class ValidationError extends Error {
field: string;
constructor(message: string, field: string) {
super(message);
this.field = field;
}
}
function handle(error: Error) {
if (error instanceof ValidationError) {
console.log(`Помилка поля ${error.field}`); // тут доступне поле field, специфічне для ValidationError
}
}Оператор in перевіряє, чи існує конкретне поле в об'єкті, — корисно для union форм об'єктів без спільного поля-дискримінанта (менш надійно за discriminated union з попереднього уроку, але застосовне, коли дискримінант додати неможливо):
type Circle = { radius: number };
type Square = { side: number };
function getArea(shape: Circle | Square): number {
if ("radius" in shape) {
return shape.radius * shape.radius * Math.PI; // звужено до Circle
}
return shape.side * shape.side; // звужено до Square
}Коли перевірка складніша за один typeof/instanceof/in, її можна винести в окрему функцію з особливим типом повернення value is Type — компілятор довіряє такій функції звужувати тип так само, як вбудованим перевіркам:
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
function isCat(animal: Cat | Dog): animal is Cat {
return "meow" in animal;
}
function makeSound(animal: Cat | Dog) {
if (isCat(animal)) {
animal.meow(); // компілятор довіряє isCat і звужує до Cat
} else {
animal.bark();
}
}Тип never позначає значення, якого теоретично ніколи не може існувати, — практичне застосування: помістити змінну типу never у гілку default switch за discriminated union (попередній урок), щоб компілятор видав помилку, якщо пізніше додати новий варіант union і забути обробити його тут:
type Shape = Circle | Square; // { kind: "circle"; ... } | { kind: "square"; ... }
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return shape.radius * shape.radius * Math.PI;
case "square":
return shape.side * shape.side;
default:
const exhaustiveCheck: never = shape; // якщо додати новий варіант Shape і забути case — помилка ТУТ
throw new Error(`Необроблений варіант: ${exhaustiveCheck}`);
}
}Покладатись на typeof для перевірки класів чи складних об'єктів — typeof повертає лише «object» для будь-якого об'єкта, не розрізняючи конкретні класи; для цього потрібен instanceof.
Писати функцію-предикат без явного типу повернення value is Type — компілятор трактує звичайний boolean як просто boolean, не звужуючи тип у місці виклику, навіть якщо логіка перевірки правильна.
Пропускати перевірку вичерпності (never у default) для union, що може розширюватись у майбутньому, — новий варіант, доданий пізніше, мовчки обробляється неправильно замість явної помилки компіляції.
Type narrowing дозволяє компілятору звужувати широкий (найчастіше union) тип до конкретнішого в межах гілки коду, спираючись на typeof (примітиви), instanceof (класи), in (наявність поля) чи власні функції-предикати з поверненням value is Type. never у гілці default за discriminated union — практичний спосіб отримати помилку компіляції, якщо пізніше з'явиться необроблений варіант union.