Skip to main content
Практика завдань

Звуження типів у TypeScript

Що таке звуження типів?

Звуження типів — це процес, за допомогою якого TypeScript уточнює тип змінної з більш загального на більш специфічний на основі перевірок у коді.

typescript
function process(value: string | number) { // Тут value: string | number if (typeof value === 'string') { // Тут value: string (тип звужено!) console.log(value.toUpperCase()); } else { // Тут value: number (залишається лише number) console.log(value.toFixed(2)); } }

Способи звуження типів

Захист за допомогою typeof

Перевірка примітивних типів за допомогою typeof.

typescript
function printValue(value: string | number | boolean) { if (typeof value === 'string') { console.log(value.toUpperCase()); } else if (typeof value === 'number') { console.log(value.toFixed(2)); } else { console.log(value ? 'true' : 'false'); } }

Важливо:

typeof null повертає 'object', це особливість JavaScript!

typescript
function process(value: string | null) { if (typeof value === 'object') { // Тут value все ще string | null (null — це об'єкт!) console.log(value); // може бути null } }

Захист за допомогою instanceof

Перевірка належності до класу.

typescript
class Dog { bark() { console.log('Woof!'); } } class Cat { meow() { console.log('Meow!'); } } function makeSound(animal: Dog | Cat) { if (animal instanceof Dog) { animal.bark(); // animal: Dog } else { animal.meow(); // animal: Cat } }

Робота з вбудованими класами

typescript
function processValue(value: Date | string) { if (value instanceof Date) { console.log(value.getFullYear()); // value: Date } else { console.log(value.toUpperCase()); // value: string } }

Оператор in

Перевірка наявності пропси в об'єкті.

typescript
interface Circle { radius: number; } interface Square { size: number; } type Shape = Circle | Square; function getArea(shape: Shape) { if ('radius' in shape) { // shape: Circle return Math.PI * shape.radius ** 2; } else { // shape: Square return shape.size ** 2; } }

Перевірка методів

typescript
interface Bird { fly(): void; layEggs(): void; } interface Fish { swim(): void; layEggs(): void; } function move(animal: Bird | Fish) { if ('fly' in animal) { animal.fly(); // animal: Bird } else { animal.swim(); // animal: Fish } }

Звуження через рівність

Звуження через перевірку рівності.

typescript
function process(x: string | number, y: string | boolean) { if (x === y) { // x і y можуть бути рівними лише якщо обидва є рядками console.log(x.toUpperCase()); // x: string console.log(y.toUpperCase()); // y: string } }

Перевірка на null і undefined

typescript
function printName(name: string | null | undefined) { if (name !== null && name !== undefined) { console.log(name.toUpperCase()); // name: string } // Або коротше if (name != null) { console.log(name.toUpperCase()); // name: string } }

Звуження за правдивістю

Звуження на основі перевірки правдивості/хибності.

typescript
function printLength(str: string | null | undefined) { if (str) { // str: string (null і undefined видалені) console.log(str.length); } }

Хибні значення

typescript
function process(value: string | number | null | undefined | 0 | '') { if (value) { // value: string | number (хибні значення видалені) // АЛЕ! 0 і '' також є хибними, тому їх також видалено } }

Увага:

Звуження правдивості видаляє ВСІ хибні значення: 0, '', false, null, undefined, NaN.

Більш точна перевірка

typescript
function processValue(value: string | null) { if (value !== null) { // value: string console.log(value.length); } }

Предикати типу (is)

Користувацькі захисти типу з ключовим словом is.

typescript
function isString(value: unknown): value is string { return typeof value === 'string'; } function process(value: unknown) { if (isString(value)) { // value: string console.log(value.toUpperCase()); } }

Більш складні перевірки

typescript
interface User { name: string; email: string; } function isUser(obj: unknown): obj is User { return ( typeof obj === 'object' && obj !== null && 'name' in obj && 'email' in obj && typeof (obj as User).name === 'string' && typeof (obj as User).email === 'string' ); } function greetUser(data: unknown) { if (isUser(data)) { // data: User console.log(`Hello, ${data.name}!`); } }

Дискриміновані об'єднання

Звуження на основі спільної дискримінуючої пропси.

typescript
type Success = { status: 'success'; data: string; }; type Error = { status: 'error'; message: string; }; type Result = Success | Error; function handleResult(result: Result) { if (result.status === 'success') { // result: Success console.log(result.data); } else { // result: Error console.log(result.message); } }

Оператор Switch

typescript
type Action = | { type: 'INCREMENT' } | { type: 'DECREMENT' } | { type: 'SET'; value: number }; function reducer(state: number, action: Action): number { switch (action.type) { case 'INCREMENT': // action: { type: 'INCREMENT' } return state + 1; case 'DECREMENT': // action: { type: 'DECREMENT' } return state - 1; case 'SET': // action: { type: 'SET'; value: number } return action.value; } }

Звуження при присвоєнні

Звуження при присвоєнні.

typescript
let value: string | number; value = 'hello'; // value: string (звужено до string) console.log(value.toUpperCase()); value = 42; // value: number (звужено до number) console.log(value.toFixed(2));

Аналіз потоку управління

TypeScript аналізує потік виконання коду.

typescript
function process(value: string | null) { if (value === null) { return; } // value: string (null виключено після return) console.log(value.toUpperCase()); }

Викидання виключень

typescript
function assertIsString(value: unknown): asserts value is string { if (typeof value !== 'string') { throw new Error('Not a string!'); } } function process(value: unknown) { assertIsString(value); // value: string (після підтвердження) console.log(value.toUpperCase()); }

Практичні приклади

Обробка відповіді API

typescript
type ApiResponse<T> = | { success: true; data: T } | { success: false; error: string }; async function fetchUser(id: number): Promise<ApiResponse<User>> { // ... } const response = await fetchUser(1); if (response.success) { // response: { success: true; data: User } console.log(response.data.name); } else { // response: { success: false; error: string } console.error(response.error); }

Валідація полів форми

typescript
interface FormData { name?: string; email?: string; age?: number; } function validateForm(data: FormData): boolean { if (!data.name) { console.error('Name is required'); return false; } // data.name: string (не undefined) if (data.name.length < 3) { console.error('Name too short'); return false; } if (!data.email) { console.error('Email is required'); return false; } // data.email: string if (!data.email.includes('@')) { console.error('Invalid email'); return false; } return true; }

Робота з подіями

typescript
function handleEvent(event: MouseEvent | KeyboardEvent) { if (event instanceof MouseEvent) { console.log(`Mouse: ${event.clientX}, ${event.clientY}`); } else { console.log(`Key: ${event.key}`); } }

Array.isArray()

typescript
function process(value: string | string[]) { if (Array.isArray(value)) { // value: string[] value.forEach(item => console.log(item)); } else { // value: string console.log(value); } }

Тип Never та вичерпність

typescript
type Shape = | { kind: 'circle'; radius: number } | { kind: 'square'; size: number }; function getArea(shape: Shape): number { switch (shape.kind) { case 'circle': return Math.PI * shape.radius ** 2; case 'square': return shape.size ** 2; default: // shape: never (всі випадки оброблені) const _exhaustive: never = shape; return _exhaustive; } }

Якщо ми додамо новий тип, TypeScript видасть помилку:

typescript
type Shape = | { kind: 'circle'; radius: number } | { kind: 'square'; size: number } | { kind: 'triangle'; base: number; height: number }; // Помилка у випадку за замовчуванням!

Обмеження звуження типів

Зміни змінних у колбек-ах

typescript
function process(value: string | null) { if (value !== null) { setTimeout(() => { // Помилка! value могло змінитися console.log(value.toUpperCase()); }, 1000); } value = null; // Змінено! }

Мутації об'єктів

typescript
interface Container { value: string | number; } function process(container: Container) { if (typeof container.value === 'string') { setTimeout(() => { // Помилка! value могло змінитися console.log(container.value.toUpperCase()); }, 0); } }

Найкращі практики

Використовуйте захисти типу для складних перевірок

typescript
// Погано function process(data: unknown) { if ( typeof data === 'object' && data !== null && 'name' in data && typeof (data as any).name === 'string' ) { console.log((data as { name: string }).name); } } // Добре function isUser(data: unknown): data is { name: string } { return ( typeof data === 'object' && data !== null && 'name' in data && typeof (data as any).name === 'string' ); } function process(data: unknown) { if (isUser(data)) { console.log(data.name); } }

Раннє повернення для спрощення

typescript
// Погано function process(value: string | null) { if (value !== null) { console.log(value.toUpperCase()); console.log(value.length); // багато коду... } } // Добре function process(value: string | null) { if (value === null) return; // value: string протягом усієї функції console.log(value.toUpperCase()); console.log(value.length); // багато коду... }

Використовуйте дискриміновані об'єднання

typescript
// Погано interface Result { success: boolean; data?: string; error?: string; } // Добре type Result = | { success: true; data: string } | { success: false; error: string };

Висновок

Звуження типів:

  • Автоматичне уточнення типу на основі перевірок
  • typeof для примітивів
  • instanceof для класів
  • in для властивостей об'єктів
  • Звуження через рівність і правдивість
  • Предикати типу (is) для користувацьких перевірок
  • Дискриміновані об'єднання для станів
  • Аналіз потоку управління відстежує виконання
  • Never для перевірки вичерпності

На співбесідах:

Важливо вміти:

  • Пояснити концепцію звуження типів
  • Показати різні методи звуження (typeof, instanceof, in)
  • Написати захист типу з is
  • Пояснити дискриміновані об'єднання
  • Показати перевірку вичерпності через never
  • Обговорити обмеження (колбеки, мутації)

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань