Пошук уроків, статей та іншого контенту
Розгляньте Awaited, InstanceType, ThisParameterType, OmitThisParameter, ThisType та інші вбудовані Utility Types.
Utility Types — це вбудовані узагальнені типи, які допомагають отримувати нові типи на основі вже наявних.
Вони особливо корисні, коли потрібно:
отримати тип результату функції;
витягнути типи її параметрів;
працювати з конструкторами та екземплярами класів;
змінити властивості об’єкта;
безпечно описати this;
перетворити або відфільтрувати об’єднання типів.
Utility Types існують лише на рівні типів і не додають жодного коду під час виконання програми.
AwaitedAwaited<Type> отримує тип значення, яке повертає await.
type A = Awaited<Promise<string>>;
// string
type B = Awaited<Promise<Promise<number>>>;
// number
type C = Awaited<boolean | Promise<number>>;
// boolean | numberAwaited рекурсивно розгортає вкладені Promise:
async function loadUser(): Promise<{
id: number;
name: string;
}> {
return {
id: 1,
name: "Anna",
};
}
type User = Awaited<ReturnType<typeof loadUser>>;
// {
// id: number;
// name: string;
// }Тут:
ReturnType<typeof loadUser> дає Promise<{ id: number; name: string }>;
Awaited<...> дістає тип значення всередині Promise.
Це зручно для функцій, які працюють з асинхронними даними.
ReturnTypeReturnType<Type> отримує тип, який повертає функція.
function getStatus() {
return {
code: 200,
message: "OK",
};
}
type Status = ReturnType<typeof getStatus>;
// {
// code: number;
// message: string;
// }Для асинхронної функції результатом буде Promise:
async function fetchCount(): Promise<number> {
return 42;
}
type CountPromise = ReturnType<typeof fetchCount>;
// Promise<number>
type Count = Awaited<ReturnType<typeof fetchCount>>;
// numbertypeof fetchCount у цьому прикладі означає тип самої функції, а не результат її виклику.
ParametersParameters<Type> отримує тип параметрів функції у вигляді кортежу.
function createUser(name: string, age: number, active: boolean) {
return {
name,
age,
active,
};
}
type CreateUserParameters = Parameters<typeof createUser>;
// [name: string, age: number, active: boolean]Кортеж можна використати для повторного опису аргументів:
function logCall(
fn: (...args: CreateUserParameters) => unknown,
...args: CreateUserParameters
): void {
console.log("Виклик функції:", args);
fn(...args);
}
logCall(createUser, "Anna", 28, true);Оскільки Parameters повертає кортеж, окремий параметр можна отримати за індексом:
function updateProduct(id: string, price: number): void {
console.log(id, price);
}
type ProductId = Parameters<typeof updateProduct>[0];
// string
type ProductPrice = Parameters<typeof updateProduct>[1];
// numberConstructorParametersConstructorParameters<Type> отримує параметри конструктора класу у вигляді кортежу.
class Product {
constructor(
public readonly name: string,
public readonly price: number,
) {}
}
type ProductConstructorParameters = ConstructorParameters<typeof Product>;
// [name: string, price: number]Потрібно передавати саме typeof Product, оскільки конструктор є значенням класу:
function createInstance<T extends new (...args: any[]) => any>(
Class: T,
...args: ConstructorParameters<T>
): InstanceType<T> {
return new Class(...args);
}
const product = createInstance(Product, "Keyboard", 100);
// ProductInstanceTypeInstanceType<Type> отримує тип екземпляра, створеного конструктором.
class User {
constructor(
public readonly name: string,
) {}
greet(): string {
return `Привіт, ${this.name}!`;
}
}
type UserInstance = InstanceType<typeof User>;
// Usertypeof User — це тип конструктора класу, а InstanceType<typeof User> — тип об’єкта, який буде створено через new User(...).
Ці типи часто використовують разом:
type UserArgs = ConstructorParameters<typeof User>;
// [name: string]
type UserObject = InstanceType<typeof User>;
// UserThisParameterTypeУ TypeScript функція може мати спеціальний параметр this. Він не є фактичним аргументом під час виклику, але описує, з яким контекстом функцію можна використовувати.
function formatName(this: { name: string }, prefix: string): string {
return `${prefix}${this.name}`;
}
type FormatNameThis = ThisParameterType<typeof formatName>;
// { name: string }ThisParameterType<Type> отримує тип спеціального параметра this.
const user = {
name: "Anna",
};
const formatted = formatName.call(user, "Користувач: ");
// "Користувач: Anna"Якщо функція не має явного параметра this, результатом буде unknown:
function add(a: number, b: number): number {
return a + b;
}
type AddThis = ThisParameterType<typeof add>;
// unknownOmitThisParameterOmitThisParameter<Type> прибирає спеціальний параметр this із типу функції.
function formatName(this: { name: string }, prefix: string): string {
return `${prefix}${this.name}`;
}
type FormatNameWithoutThis = OmitThisParameter<typeof formatName>;
// (prefix: string) => stringЦе корисно, коли потрібно передати функцію як звичайний callback:
const formatWithUser = formatName.bind({ name: "Anna" });
const formatter: OmitThisParameter<typeof formatName> = formatWithUser;
console.log(formatter("Користувач: "));Після bind контекст this уже зафіксований, тому для отриманого callback він більше не потрібен.
ThisParameterType та OmitThisParameterThisParameterType витягує тип this;
OmitThisParameter видаляє this із типу функції;
обидва Utility Types працюють лише з явним параметром this.
ThisTypeThisType<Type> не перетворює тип безпосередньо. Це спеціальний маркер, який повідомляє TypeScript, який тип має мати this всередині об’єктного літерала.
Розглянемо фабрику об’єктів:
type ObjectDescriptor<Data, Methods> = {
data?: Data;
methods?: Methods & ThisType<Data & Methods>;
};
function makeObject<Data, Methods>(
descriptor: ObjectDescriptor<Data, Methods>,
): Data & Methods {
const data = descriptor.data ?? ({} as Data);
const methods = descriptor.methods ?? ({} as Methods);
return Object.assign(data, methods);
}
const counter = makeObject({
data: {
value: 0,
},
methods: {
increment() {
this.value += 1;
},
reset() {
this.value = 0;
},
getValue() {
return this.value;
},
},
});
counter.increment();
console.log(counter.getValue());
counter.reset();
console.log(counter.getValue());Завдяки ThisType<Data & Methods> TypeScript розуміє, що всередині increment, reset і getValue змінна this має властивість value.
ThisType:
не створює значення під час виконання;
працює лише в контексті типізації об’єктних літералів;
не змінює тип самого об’єкта;
корисний для фабрик, конфігурацій і наборів методів.
PartialPartial<Type> робить усі властивості типу необов’язковими.
interface UserProfile {
name: string;
email: string;
age: number;
}
type UserProfileUpdate = Partial<UserProfile>;
// {
// name?: string;
// email?: string;
// age?: number;
// }Це зручно для функцій часткового оновлення:
function updateProfile(
profile: UserProfile,
changes: Partial<UserProfile>,
): UserProfile {
return {
...profile,
...changes,
};
}
const profile: UserProfile = {
name: "Anna",
email: "anna@example.com",
age: 28,
};
const updatedProfile = updateProfile(profile, {
age: 29,
});Partial не дозволяє передавати властивості, яких немає в початковому типі:
updateProfile(profile, {
age: 29,
// city: "Kyiv", // Помилка: такої властивості немає в UserProfile
});RequiredRequired<Type> робить усі властивості обов’язковими.
interface AppConfig {
host?: string;
port?: number;
}
type CompleteAppConfig = Required<AppConfig>;
// {
// host: string;
// port: number;
// }const config: CompleteAppConfig = {
host: "localhost",
port: 3000,
};ReadonlyReadonly<Type> забороняє змінювати властивості типу після створення об’єкта.
interface Point {
x: number;
y: number;
}
const point: Readonly<Point> = {
x: 10,
y: 20,
};
// point.x = 15;
// Помилка: властивість доступна лише для читанняReadonly змінює перевірку типів, але не робить об’єкт незмінним під час виконання JavaScript.
PickPick<Type, Keys> створює тип лише з указаними властивостями.
interface Article {
id: number;
title: string;
content: string;
author: string;
createdAt: Date;
}
type ArticlePreview = Pick<Article, "id" | "title" | "author">;
// {
// id: number;
// title: string;
// author: string;
// }Це корисно, коли різним частинам програми потрібні різні представлення одного об’єкта.
OmitOmit<Type, Keys> створює тип, виключаючи вказані властивості.
type PublicArticle = Omit<Article, "content" | "createdAt">;
// {
// id: number;
// title: string;
// author: string;
// }Pick і Omit часто використовують для створення типів запитів:
type CreateArticleInput = Omit<Article, "id" | "createdAt">;
// {
// title: string;
// content: string;
// author: string;
// }Pick та OmitВикористовуйте Pick, коли потрібно явно перелічити дозволені властивості:
type ArticleTitle = Pick<Article, "title">;Використовуйте Omit, коли основна модель має багато властивостей, а виключити потрібно лише кілька:
type ArticleWithoutContent = Omit<Article, "content">;RecordRecord<Keys, Type> створює об’єктний тип із набором ключів Keys, де кожне значення має тип Type.
type Language = "uk" | "en" | "pl";
const languageNames: Record<Language, string> = {
uk: "Українська",
en: "English",
pl: "Polski",
};TypeScript перевіряє, що всі ключі присутні:
const incompleteLanguageNames: Record<Language, string> = {
uk: "Українська",
en: "English",
// Помилка: відсутній ключ pl
};Record зручний для словників і мап:
type UserRole = "admin" | "editor" | "viewer";
type Permissions = {
canRead: boolean;
canWrite: boolean;
};
const permissions: Record<UserRole, Permissions> = {
admin: {
canRead: true,
canWrite: true,
},
editor: {
canRead: true,
canWrite: true,
},
viewer: {
canRead: true,
canWrite: false,
},
};ExcludeExclude<UnionType, ExcludedMembers> прибирає з об’єднання вказані типи.
type Status = "pending" | "success" | "error";
type FinishedStatus = Exclude<Status, "pending">;
// "success" | "error"Це працює з кожним членом об’єднання окремо:
type Result = Exclude<string | number | boolean, string>;
// number | booleanExtractExtract<Type, Union> залишає лише ті типи, які можна присвоїти другому параметру.
type EventName = "click" | "focus" | "keydown";
type KeyboardEventName = Extract<EventName, "keydown" | "keyup">;
// "keydown"Ще один приклад:
type Value = string | number | boolean;
type PrimitiveText = Extract<Value, string>;
// stringExclude прибирає сумісні типи, а Extract залишає їх.
NonNullableNonNullable<Type> прибирає з типу null і undefined.
type MaybeName = string | null | undefined;
type Name = NonNullable<MaybeName>;
// stringЦе корисно після перевірки на наявність значення:
function printName(name: string | null | undefined): void {
if (name === null || name === undefined) {
return;
}
const safeName: NonNullable<typeof name> = name;
console.log(safeName.toUpperCase());
}Uppercase, Lowercase, Capitalize та UncapitalizeЦі Utility Types працюють із рядковими літеральними типами.
type Method = "get" | "post";
type UpperMethod = Uppercase<Method>;
// "GET" | "POST"
type Header = Capitalize<"content-type">;
// "Content-type"
type LowerMethod = Lowercase<"GET" | "POST">;
// "get" | "post"
type Uncapitalized = Uncapitalize<"UserName">;
// "userName"Вони застосовуються лише на рівні типів, а не до звичайних рядків під час виконання:
type ApiMethod = "get" | "post" | "delete";
type ApiMethodName = Uppercase<ApiMethod>;
// "GET" | "POST" | "DELETE"У наступному прикладі кілька Utility Types використовуються разом:
interface User {
id: number;
name: string;
email: string;
active: boolean;
}
async function loadUsers(): Promise<User[]> {
return [
{
id: 1,
name: "Anna",
email: "anna@example.com",
active: true,
},
];
}
function updateUser(
id: User["id"],
changes: Partial<Omit<User, "id">>,
): User {
return {
id,
name: changes.name ?? "Unknown",
email: changes.email ?? "unknown@example.com",
active: changes.active ?? false,
};
}
type LoadedUsers = Awaited<ReturnType<typeof loadUsers>>;
// User[]
type UpdateUserParameters = Parameters<typeof updateUser>;
// [id: number, changes: Partial<Omit<User, "id">>]
type UserChanges = Parameters<typeof updateUser>[1];
// Partial<Omit<User, "id">>
async function main(): Promise<void> {
const users: LoadedUsers = await loadUsers();
const updated = updateUser(users[0].id, {
active: false,
});
console.log(updated);
}
void main();У цьому прикладі:
ReturnType<typeof loadUsers> отримує тип Promise<User[]>;
Awaited<...> перетворює його на User[];
Parameters<typeof updateUser> отримує всі параметри функції;
Partial дозволяє оновлювати лише частину полів;
Omit<User, "id"> забороняє змінювати ідентифікатор.
typeof функціїНеправильно:
function getValue(): string {
return "value";
}
// type Value = ReturnType<getValue>;ReturnType очікує тип функції, тому потрібно використати typeof:
type Value = ReturnType<typeof getValue>;
// stringclass Account {
constructor(public readonly id: number) {}
}
type Constructor = typeof Account;
// тип конструктора
type AccountObject = InstanceType<typeof Account>;
// тип екземпляра Accounttypeof Account не є типом об’єкта, створеного через new Account(...).
PartialPartial змінює лише властивості верхнього рівня:
interface Settings {
theme: {
name: string;
darkMode: boolean;
};
}
type PartialSettings = Partial<Settings>;
const settings: PartialSettings = {
theme: {
name: "default",
darkMode: true,
},
};У цьому прикладі theme може бути відсутнім, але якщо його передано, його внутрішні властивості все ще є обов’язковими. Partial не є рекурсивним.
Readonly забороняє присвоєння на рівні перевірки TypeScript, але не заморожує об’єкт у JavaScript:
const config: Readonly<{ port: number }> = {
port: 3000,
};
// config.port = 4000; // Помилка TypeScriptЦе гарантія типів, а не механізм runtime-захисту.
this під час передачі методуМетод може розраховувати на контекст this:
const user = {
name: "Anna",
greet(): string {
return this.name;
},
};
const greet = user.greet;
// Під час окремого виклику контекст user може бути втраченийЯкщо функція має явний параметр this, використовуйте ThisParameterType, OmitThisParameter або прив’яжіть контекст через bind.
Awaited розгортає типи значень усередині Promise.
ReturnType отримує тип результату функції.
Parameters отримує кортеж параметрів функції.
ConstructorParameters отримує параметри конструктора.
InstanceType отримує тип екземпляра класу.
ThisParameterType витягує тип явного параметра this.
OmitThisParameter видаляє this із типу функції.
ThisType задає тип this в об’єктних літералах.
Partial, Required і Readonly змінюють обов’язковість та доступність властивостей.
Pick і Omit створюють типи з вибраними або виключеними властивостями.
Record описує об’єкти з відомим набором ключів.
Exclude, Extract і NonNullable фільтрують типи.
Uppercase, Lowercase, Capitalize та Uncapitalize перетворюють рядкові літеральні типи.