Пошук уроків, статей та іншого контенту
Керуйте перевіркою типів через type assertions, as const і satisfies, зберігаючи якомога точніший опис значень.
Type assertion — це вказівка TypeScript, який тип, на думку розробника, має значення.
Синтаксис у TypeScript:
const value = expression as SomeType;У файлах без JSX також можливий синтаксис:
const value = <SomeType>expression;У .tsx синтаксис із кутовими дужками конфліктує з JSX, тому для сучасного коду зазвичай використовують as.
Type assertion:
не змінює значення під час виконання;
не виконує перевірку або перетворення даних;
впливає лише на перевірку типів під час компіляції;
може дозволити TypeScript прийняти потенційно небезпечний код.
const input: unknown = "TypeScript";
const text = input as string;
console.log(text.toUpperCase());У цьому прикладі as string не перетворює значення на рядок. Воно лише повідомляє компілятору, що розробник вважає значення рядком.
Якщо фактичне значення має інший тип, помилка з’явиться під час виконання:
const input: unknown = 42;
const text = input as string;
// TypeScript дозволяє цей код,
// але під час виконання виникне TypeError.
console.log(text.toUpperCase());Тому assertion не замінює перевірку даних.
Assertion доречний, коли тип значення відомий із зовнішнього контексту, але TypeScript не може його вивести.
Наприклад, після пошуку елемента в DOM:
const element = document.querySelector("#email") as HTMLInputElement;
element.value = "user@example.com";TypeScript знає, що querySelector може повернути Element або null, але розробник може бути впевнений, що елемент існує і є HTMLInputElement.
Безпечніший варіант — перевірити значення:
const element = document.querySelector("#email");
if (!(element instanceof HTMLInputElement)) {
throw new Error("Елемент #email не знайдено або він не є input");
}
element.value = "user@example.com";Перевірка захищає і типи, і виконання програми.
Ці два записи мають принципово різний сенс:
const value = "42" as unknown as number;const value = Number("42");Перший запис лише змінює думку компілятора про тип. Фактично value усе ще містить рядок.
Другий запис створює нове значення типу number під час виконання.
Якщо значення потрібно перетворити, використовуйте runtime-перетворення. Якщо потрібно повідомити компілятору про вже відому властивість значення — використовуйте assertion.
type User = {
id: number;
name: string;
};
const rawData = {
id: "1",
name: "Ada",
} as User;TypeScript прийме assertion, хоча id фактично є рядком. Такий код може приховати помилку в даних.
Особливо небезпечно використовувати assertions для значень із:
HTTP-відповідей;
localStorage;
JSON;
змінних типу unknown;
зовнішніх бібліотек;
даних, які вводить користувач.
Для таких значень спочатку потрібна runtime-перевірка, а вже потім типізація.
as constas const повідомляє TypeScript, що вираз потрібно вивести з максимально вузькими літеральними типами.
Розглянемо звичайний об’єкт:
const command = {
type: "start",
retryCount: 3,
};Його тип буде приблизно таким:
{
type: string;
retryCount: number;
}Хоча змінна command оголошена через const, властивості об’єкта залишаються змінними, тому TypeScript використовує ширші типи string і number.
З as const тип стає точнішим:
const command = {
type: "start",
retryCount: 3,
} as const;Тепер тип:
{
readonly type: "start";
readonly retryCount: 3;
}as const має три основні наслідки:
літеральні значення не розширюються до string, number або boolean;
властивості об’єктів стають readonly;
елементи масивів стають readonly і зберігають літеральні типи.
const status = "loading" as const;
// тип: "loading"
const codes = [200, 201, 204] as const;
// тип: readonly [200, 201, 204]Масив із as const — це readonly tuple. У ньому відомі і порядок, і точні значення елементів.
const codes = [200, 201, 204] as const;
const firstCode = codes[0];
// тип: 200
// Помилка: readonly tuple не можна змінювати
// codes.push(404);as constas const зручно використовувати для створення набору дозволених значень:
const roles = ["admin", "editor", "viewer"] as const;
type Role = (typeof roles)[number];
const role: Role = "editor";
// Помилка: "owner" не входить до Role
// const invalidRole: Role = "owner";Вираз (typeof roles)[number] означає: взяти тип будь-якого елемента tuple. У результаті отримуємо:
type Role = "admin" | "editor" | "viewer";Так джерелом значень залишається один масив, а тип автоматично синхронізується з ним.
as const для discriminated unionconst actions = {
start: { type: "START" },
stop: { type: "STOP" },
reset: { type: "RESET" },
} as const;
type Action = (typeof actions)[keyof typeof actions];
function reduce(action: Action): string {
switch (action.type) {
case "START":
return "Процес запущено";
case "STOP":
return "Процес зупинено";
case "RESET":
return "Стан скинуто";
}
}Завдяки літеральним типам TypeScript знає всі можливі значення action.type.
Без as const властивість type могла б бути виведена як звичайний string, і така точна перевірка була б неможливою.
satisfiesОператор satisfies перевіряє, чи відповідає вираз певному типу, але при цьому зберігає якомога точніший тип самого виразу.
Синтаксис:
const value = expression satisfies SomeType;На відміну від assertion, satisfies не наказує TypeScript вважати значення іншим типом. Він лише перевіряє сумісність.
type Theme = {
mode: "light" | "dark";
accentColor: string;
};
const theme = {
mode: "dark",
accentColor: "#111827",
} satisfies Theme;Об’єкт відповідає Theme, тому помилки немає. Водночас TypeScript зберігає точні типи властивостей, виведені з конкретного об’єкта.
type Config = {
mode: "development" | "production";
port: number;
};
const annotatedConfig: Config = {
mode: "development",
port: 3000,
};
const checkedConfig = {
mode: "development",
port: 3000,
} satisfies Config;В обох випадках значення перевіряється на відповідність Config, але підхід відрізняється:
annotation : Config оголошує тип змінної як Config;
satisfies Config перевіряє відповідність, зберігаючи тип конкретного виразу.
Це особливо помітно, коли тип містить словник або union об’єктів.
type Route = {
url: string;
authRequired: boolean;
};
const routes = {
home: {
url: "/",
authRequired: false,
},
settings: {
url: "/settings",
authRequired: true,
},
} satisfies Record<string, Route>;
const homeUrl = routes.home.url;routes перевірений як словник маршрутів, але TypeScript також знає конкретні ключі home і settings.
Якщо записати annotation:
const routes: Record<string, Route> = {
home: {
url: "/",
authRequired: false,
},
settings: {
url: "/settings",
authRequired: true,
},
};тип змінної описує загальний словник із ключем string. Частина конкретної інформації про об’єкт втрачається.
type Config = {
mode: "development" | "production";
port: number;
};
const assertedConfig = {
mode: "testing",
port: "3000",
} as Config;Assertion дозволяє TypeScript прийняти значення, навіть якщо воно не відповідає типу.
З satisfies неправильне значення буде відхилене:
const checkedConfig = {
// Помилка: "testing" не дозволений
mode: "testing",
// Помилка: очікується number
port: "3000",
} satisfies Config;Отже:
as Config — «вважай це Config»;
satisfies Config — «перевір, що це відповідає Config, але не втрачай точність».
as const і satisfiesЦі оператори розв’язують різні задачі й добре доповнюють один одного:
as const робить значення максимально вузьким і readonly;
satisfies перевіряє його відповідність очікуваній структурі.
type HttpMethod = "GET" | "POST" | "DELETE";
type RequestDefinition = {
method: HttpMethod;
path: string;
requiresAuth: boolean;
};
const requests = {
listUsers: {
method: "GET",
path: "/users",
requiresAuth: true,
},
createUser: {
method: "POST",
path: "/users",
requiresAuth: true,
},
deleteUser: {
method: "DELETE",
path: "/users/:id",
requiresAuth: true,
},
} as const satisfies Record<string, RequestDefinition>;Тут одночасно виконується кілька умов:
кожен маршрут має method, path і requiresAuth;
method може бути лише одним із дозволених HTTP-методів;
конкретні значення, наприклад "GET", зберігаються як літерали;
властивості стають readonly;
помилка в назві властивості або її типі буде знайдена компілятором.
Практичний приклад:
type HttpMethod = "GET" | "POST" | "DELETE";
type RequestDefinition = {
method: HttpMethod;
path: string;
requiresAuth: boolean;
};
const requests = {
listUsers: {
method: "GET",
path: "/users",
requiresAuth: true,
},
createUser: {
method: "POST",
path: "/users",
requiresAuth: true,
},
} as const satisfies Record<string, RequestDefinition>;
type RequestName = keyof typeof requests;
function getRequestPath(name: RequestName): string {
return requests[name].path;
}
console.log(getRequestPath("listUsers"));
console.log(getRequestPath("createUser"));
// Помилка: "unknown" не є ключем requests
// getRequestPath("unknown");
type Method = (typeof requests)[RequestName]["method"];
const method: Method = "GET";
// Помилка: "PATCH" не входить до Method
// const invalidMethod: Method = "PATCH";Цей файл можна перевірити компілятором у строгому режимі:
npx tsc --strict --noEmit requests.tsНехай є тип:
type UserPreferences = {
theme: "light" | "dark";
notifications: boolean;
};const preferences: UserPreferences = {
theme: "dark",
notifications: true,
};Використовуйте, коли змінна концептуально має саме цей тип і ширший опис вас влаштовує.
const preferences = {
theme: "dark",
notifications: true,
} as UserPreferences;Використовуйте лише тоді, коли ви маєте обґрунтовану гарантію, якої TypeScript не бачить. Assertion може приховати помилку.
satisfiesconst preferences = {
theme: "dark",
notifications: true,
} satisfies UserPreferences;Використовуйте, коли потрібно:
перевірити форму об’єкта;
зберегти конкретні типи його властивостей;
отримати помилки для зайвих або неправильних властивостей.
as const satisfiesconst preferences = {
theme: "dark",
notifications: true,
} as const satisfies UserPreferences;Використовуйте, коли додатково потрібні:
літеральні типи;
readonly-властивості;
точне виведення вкладених об’єктів і масивів.
const data: unknown = JSON.parse('{"id":"wrong"}');
const user = data as { id: number };
// Небезпечний код: assertion не перевірив id
console.log(user.id + 1);JSON.parse не перевіряє структуру результату. Assertion також її не перевіряє.
as const для змінного стануconst state = {
status: "idle",
} as const;
// Помилка: властивість readonly
// state.status = "loading";as const підходить для конфігурацій, таблиць відповідностей і наборів констант. Для об’єктів, які потрібно змінювати, він непридатний.
satisfies способом перетворення типуtype Options = {
enabled: boolean;
};
const options = {
enabled: true,
} satisfies Options;satisfies не перетворює об’єкт на новий тип і не створює нового значення. Він лише додає перевірку сумісності на етапі компіляції.
type Status = "idle" | "loading" | "success" | "error";
const status = getStatus() as Status;Якщо getStatus повертає звичайний string, assertion приховує проблему. Краще або правильно типізувати функцію, або перевірити отримане значення перед використанням.
as const перевірить структуруconst config = {
port: "3000",
} as const;as const робить "3000" літеральним readonly-значенням, але не повідомляє, що поле port має бути числом.
Якщо потрібна структурна перевірка:
type Config = {
port: number;
};
const config = {
// Помилка: очікується number
port: "3000",
} as const satisfies Config;Вибирайте конструкцію за її призначенням:
Потрібно оголосити тип змінної — використовуйте анотацію:
const value: SomeType = expression;Потрібно звузити літерали та заборонити зміну — використовуйте:
const value = expression as const;Потрібно перевірити форму, але зберегти точне виведення — використовуйте:
const value = expression satisfies SomeType;Потрібні і структурна перевірка, і літеральні readonly-типи — використовуйте:
const value = expression as const satisfies SomeType;Потрібно перетворити або перевірити дані під час виконання — жодна з цих конструкцій не підходить сама по собі. Потрібна звичайна runtime-логіка.
Type assertion as Type змінює лише уявлення компілятора про значення і не виконує runtime-перевірок.
Assertion може бути виправданим для DOM або інших випадків, де тип відомий із зовнішнього контексту, але його слід використовувати обережно.
as const зберігає літеральні типи, робить властивості readonly і перетворює масиви на readonly tuple.
satisfies перевіряє відповідність значення типу, не втрачаючи точного виведення конкретного виразу.
as const satisfies Type поєднує точні readonly-типи зі структурною перевіркою.
Жодна з цих конструкцій не замінює перевірку даних, отриманих під час виконання.