Типові асерції в TypeScript
Що таке типові твердження?
Типові твердження — це спосіб повідомити компілятору TypeScript: "Я знаю тип цього значення краще, ніж ти". Це не приведення типу і не змінює значення під час виконання — це лише підказка для компілятора.
Синтаксис
TypeScript підтримує два синтаксиси для типових тверджень:
Синтаксис as
const value: any = "hello";
const length = (value as string).length; // 5Синтаксис з кутовими дужками
const value: any = "hello";
const length = (<string>value).length; // 5Важливо:
У JSX/TSX можна використовувати лише синтаксис as, оскільки кутові дужки конфліктують із синтаксисом JSX.
Коли використовувати типові твердження?
Робота з DOM
// TypeScript не знає, який елемент буде повернуто
const input = document.getElementById('email'); // HTMLElement | null
// Твердження конкретного типу
const emailInput = document.getElementById('email') as HTMLInputElement;
emailInput.value = 'test@example.com';
// Альтернатива
const emailInput2 = <HTMLInputElement>document.getElementById('email');Робота з API та будь-яким типом
const response: any = await fetch('/api/user').then(r => r.json());
interface User {
id: number;
name: string;
email: string;
}
const user = response as User;
console.log(user.name);Звуження об'єднаних типів
type Result = { success: true; data: string } | { success: false; error: string };
function handleResult(result: Result) {
if (result.success) {
// TypeScript знає, що це { success: true; data: string }
console.log(result.data);
} else {
// TypeScript знає, що це { success: false; error: string }
console.log(result.error);
}
}
// Але іноді потрібне явне твердження:
const result = getResult();
const successResult = result as { success: true; data: string };Робота з літералами
// TypeScript виводить тип як string
const method = 'GET'; // string
// Потрібен конкретний літерал
const method2 = 'GET' as const; // 'GET'
// Або
const method3 = 'GET' as 'GET' | 'POST';Типові твердження vs Приведення типу
Типові твердження (TypeScript)
const value: any = "42";
const num = value as number; // Компільовано, але помилка під час виконання!
console.log(num * 2); // NaN (рядок помножений на число)Приведення типу (інші мови)
// В інших мовах приведення конвертує значення
String num = "42";
int converted = (int) num; // Насправді конвертує рядок у числоВажливо:
Типові твердження не виконують конверсію і не перевіряють тип під час виконання. Це лише підказка для компілятора.
Подвійні твердження
Іноді TypeScript не дозволяє пряме твердження між несумісними типами:
const value: string = "hello";
// Помилка: Конверсія типу 'string' у тип 'number' може бути помилкою
// const num = value as number;
// Подвійне твердження через unknown (або any)
const num = value as unknown as number;Увага:
Подвійні твердження є ознакою проблем з типами. Використовуйте лише як останній засіб.
Оператор твердження про ненульовість
Оператор ! повідомляє TypeScript, що значення точно не є null або undefined.
Синтаксис
// TypeScript вважає, що це може бути null
const element = document.getElementById('root'); // HTMLElement | null
// Ми впевнені, що елемент існує
const elementNonNull = document.getElementById('root')!; // HTMLElement
elementNonNull.innerHTML = 'Hello';Приклади використання
interface User {
name: string;
email?: string;
}
const user: User = { name: 'John', email: 'john@example.com' };
// Помилка: Об'єкт може бути 'undefined'
// const emailLength = user.email.length;
// З перевіркою
if (user.email) {
const emailLength = user.email.length;
}
// З твердженням про ненульовість (якщо впевнені)
const emailLength = user.email!.length;З необов'язковим ланцюгом
interface Config {
api?: {
url?: string;
};
}
const config: Config = {
api: { url: 'https://api.example.com' }
};
// Без твердження про ненульовість
const url = config.api?.url; // string | undefined
// З твердженням про ненульовість
const urlNonNull = config.api!.url!; // stringУвага:
Твердження про ненульовість обходить перевірки TypeScript. Якщо значення виявиться null або undefined, ви отримаєте помилку під час виконання.
Твердження const
as const створює типи літералів, що не можуть бути змінені.
Примітиви
// Звичайне оголошення
let x = 'hello'; // string
// З as const
let y = 'hello' as const; // 'hello' (літеральний тип)Об'єкти
// Звичайний об'єкт
const config = {
host: 'localhost',
port: 3000
};
// { host: string; port: number; }
// З as const
const configConst = {
host: 'localhost',
port: 3000
} as const;
// { readonly host: 'localhost'; readonly port: 3000; }
// Не можна змінювати
// configConst.port = 8080; // ПомилкаМасиви
// Звичайний масив
const colors = ['red', 'green', 'blue'];
// string[]
// З as const
const colorsConst = ['red', 'green', 'blue'] as const;
// readonly ['red', 'green', 'blue']
type Color = typeof colorsConst[number];
// 'red' | 'green' | 'blue'Практичне застосування
// Створення об'єднаного типу з масиву
const ROLES = ['admin', 'user', 'guest'] as const;
type Role = typeof ROLES[number]; // 'admin' | 'user' | 'guest'
function checkRole(role: Role) {
// ...
}
checkRole('admin'); // OK
// checkRole('moderator'); // ПомилкаОператор Satisfies (TypeScript 4.9+)
satisfies перевіряє відповідність типу, зберігаючи виведення типу.
type Colors = 'red' | 'green' | 'blue';
const colors = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255]
} satisfies Record<Colors, string | number[]>;
// TypeScript зберіг точні типи
colors.red; // number[]
colors.green; // string
colors.blue; // number[]
// Якщо б ми використали типове твердження:
const colors2: Record<Colors, string | number[]> = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255]
};
colors2.red; // string | number[] (втрата точності)Коли НЕ використовувати типові твердження
Замість правильного типізування
// Погано
function getUser() {
return { id: 1, name: 'John' } as any;
}
// Добре
interface User {
id: number;
name: string;
}
function getUser(): User {
return { id: 1, name: 'John' };
}Для виправлення помилок типізації
// Погано - приховування проблеми
const value: number = "hello" as any as number;
// Добре - виправлення проблеми
const value: string = "hello";
const num: number = parseInt(value, 10);Коли можна використовувати захисні механізми типів
function processValue(value: string | number) {
// Погано
const str = value as string;
return str.toUpperCase();
// Добре
if (typeof value === 'string') {
return value.toUpperCase();
}
return value.toString();
}Безпечні альтернативи
Захисні механізми типів
function isString(value: any): value is string {
return typeof value === 'string';
}
const value: any = "hello";
if (isString(value)) {
// TypeScript знає, що value є рядком
console.log(value.toUpperCase());
}Розрізнені об'єднання
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; size: number };
function getArea(shape: Shape) {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.size ** 2;
}
}Необов'язковий ланцюг
// Замість твердження про ненульовість
const value = obj.prop!.nested!.value;
// Використовуйте необов'язковий ланцюг
const value = obj.prop?.nested?.value; // string | undefinedЗагальні помилки
Твердження несумісних типів
const num = 42;
// Помилка: не можна конвертувати число в рядок
// const str = num as string;
// Потрібне подвійне твердження (але це погано!)
const str = num as unknown as string;Ігнорування помилок через as any
// Погано
function processData(data: ComplexType) {
return (data as any).someMethod();
}
// Добре
function processData(data: ComplexType) {
if ('someMethod' in data && typeof data.someMethod === 'function') {
return data.someMethod();
}
}Втрата безпеки типів
// Погано - втрата безпеки типів
const users = getUsers() as any[];
// Добре
interface User {
id: number;
name: string;
}
const users = getUsers() as User[];Практичні патерни
Безпечне витягування з DOM
function getElement<T extends HTMLElement>(id: string): T | null {
return document.getElementById(id) as T | null;
}
const input = getElement<HTMLInputElement>('email');
if (input) {
input.value = 'test';
}Робота з unknown
function parseJSON(json: string): unknown {
return JSON.parse(json);
}
const data = parseJSON('{"name": "John"}');
// Захисний механізм для безпеки
function isUser(data: unknown): data is { name: string } {
return typeof data === 'object' &&
data !== null &&
'name' in data &&
typeof (data as any).name === 'string';
}
if (isUser(data)) {
console.log(data.name);
}Висновок
Типові твердження:
- Не конвертують значення, лише вказують тип компілятору
- Синтаксис:
as(бажано) або<>(не в JSX) - Корисні при роботі з DOM та
any - Твердження про ненульовість
!усуваєnull | undefined as constстворює літерали, що не можуть бути зміненіsatisfies(4.9+) перевіряє тип, зберігаючи виведення- Використовуйте обережно — можуть приховувати проблеми
На співбесідах:
Важливо вміти:
- Пояснити різницю між типовими твердженнями та приведенням типу
- Показати обидва синтаксиси (
asта<>) - Пояснити, коли використовувати
as const - Обговорити оператор твердження про ненульовість
- Надати приклади безпечних альтернатив (захисні механізми типів)
- Пояснити ризики надмірного використання тверджень
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.