Пошук уроків, статей та іншого контенту
Описуйте функції, передані як аргументи, і створюйте типобезпечні callback-функції.
Callback-функція — це функція, яку передають іншій функції як аргумент, щоб викликати її пізніше або в певний момент виконання.
Наприклад, map отримує callback, який виконується для кожного елемента масиву:
const numbers = [1, 2, 3];
const doubled = numbers.map((number) => number * 2);
console.log(doubled); // [2, 4, 6]У цьому прикладі:
map — функція, яка приймає callback;
(number) => number * 2 — callback-функція;
параметр number має тип number;
результат callback має бути сумісним із типом елементів нового масиву.
TypeScript може вивести тип callback автоматично, але під час створення власних функцій його потрібно описувати явно.
Тип функції записують так:
(parameter1: Type1, parameter2: Type2) => ReturnTypeНаприклад:
type Formatter = (value: string) => string;Formatter — це тип функції, яка:
отримує один параметр типу string;
повертає значення типу string.
Тепер цей тип можна використати для змінної:
type Formatter = (value: string) => string;
const uppercase: Formatter = (value) => {
return value.toUpperCase();
};
console.log(uppercase("typescript")); // TYPESCRIPTТип параметра value визначається через контекст: TypeScript знає, що uppercase має тип Formatter.
Якщо функція приймає callback, тип callback записують у списку параметрів:
function transform(
value: string,
callback: (value: string) => string
): string {
return callback(value);
}
const result = transform("hello", (text) => text.toUpperCase());
console.log(result); // HELLOСигнатура transform означає:
перший параметр — рядок;
другий параметр — функція;
callback отримує рядок;
callback повертає рядок;
transform також повертає рядок.
Callback можна винести в окремий іменований тип, якщо він використовується кілька разів:
type StringTransformer = (value: string) => string;
function transform(
value: string,
callback: StringTransformer
): string {
return callback(value);
}Іменовані типи роблять складні сигнатури зрозумілішими.
Тип callback має описувати всі параметри, які передає функція:
type ItemProcessor = (
item: string,
index: number
) => string;
function processItems(
items: string[],
processor: ItemProcessor
): string[] {
return items.map(processor);
}
const result = processItems(
["apple", "banana", "orange"],
(item, index) => `${index + 1}. ${item}`
);
console.log(result);
// ["1. apple", "2. banana", "3. orange"]У цьому прикладі callback отримує:
поточний елемент типу string;
його індекс типу number.
Порядок параметрів має значення. Якщо функція передає (item, index), тип callback також повинен описувати їх у такому порядку.
Для callback без результату використовують тип void:
type Logger = (message: string) => void;
function runTask(task: Logger): void {
task("Завдання виконано");
}
runTask((message) => {
console.log(message);
});void означає, що результат callback не використовується функцією runTask.
Функція, яка фактично щось повертає, також може бути передана в callback із результатом void:
type Action = () => void;
const action: Action = () => {
return console.log("Дія виконана");
};Це дозволено, тому що значення, яке повертає callback, буде проігнороване. Проте якщо результат callback має значення, яке використовує зовнішня функція, потрібно вказати конкретний тип результату, а не void.
Якщо зовнішня функція використовує результат callback, тип результату потрібно вказати явно:
type Predicate<T> = (value: T) => boolean;
function findFirst<T>(
items: T[],
predicate: Predicate<T>
): T | undefined {
for (const item of items) {
if (predicate(item)) {
return item;
}
}
return undefined;
}
const firstLongWord = findFirst(
["cat", "elephant", "dog"],
(word) => word.length > 3
);
console.log(firstLongWord); // elephantCallback predicate повертає boolean. Зовнішня функція використовує це значення, щоб вирішити, який елемент повернути.
Коли callback передають безпосередньо як аргумент, TypeScript часто визначає типи його параметрів із контексту:
const numbers = [10, 20, 30];
const result = numbers.map((number, index) => {
return `${index}: ${number}`;
});TypeScript автоматично визначає:
number як number;
index як number;
результат callback як string.
Тому немає потреби дублювати типи:
const result = numbers.map(
(number: number, index: number): string => {
return `${index}: ${number}`;
}
);Обидва варіанти коректні, але перший зазвичай читабельніший.
Контекстна типізація працює, коли TypeScript уже знає очікуваний тип callback. Якщо функцію спочатку зберігають у змінній без типу, параметри можуть не мати достатнього контексту:
const format = (value) => value.toUpperCase();
// Помилка за noImplicitAny: параметр value має неявний тип anyУ такому випадку потрібно вказати тип:
const format = (value: string): string => {
return value.toUpperCase();
};Або використати іменований тип:
type Formatter = (value: string) => string;
const format: Formatter = (value) => {
return value.toUpperCase();
};Дженерик дає змогу створити функцію, яка працює з різними типами, але зберігає зв’язок між типом вхідних даних і типом callback.
function apply<T, R>(
value: T,
callback: (value: T) => R
): R {
return callback(value);
}
const length = apply("TypeScript", (text) => text.length);
const isPositive = apply(10, (number) => number > 0);
console.log(length); // 10
console.log(isPositive); // trueУ першому виклику:
T — string;
R — number.
У другому виклику:
T — number;
R — boolean.
TypeScript перевіряє, що callback приймає саме тип T, переданий у apply.
Небезпечний callback TypeScript відхилить:
function apply<T, R>(
value: T,
callback: (value: T) => R
): R {
return callback(value);
}
// Помилка: callback очікує number, але apply отримує string
// const result = apply("hello", (value: number) => value * 2);Асинхронний callback повертає Promise. Для нього потрібно вказати тип Promise<...>:
type AsyncHandler<T> = (value: T) => Promise<void>;
async function processValues<T>(
values: T[],
handler: AsyncHandler<T>
): Promise<void> {
for (const value of values) {
await handler(value);
}
}
async function main(): Promise<void> {
await processValues([1, 2, 3], async (value) => {
console.log(`Оброблено: ${value}`);
});
}
main();Якщо callback повертає результат, його тип потрібно вказати всередині Promise:
type AsyncTransformer<T, R> = (value: T) => Promise<R>;
async function transformAsync<T, R>(
value: T,
callback: AsyncTransformer<T, R>
): Promise<R> {
return callback(value);
}
async function main(): Promise<void> {
const result = await transformAsync("hello", async (text) => {
return text.toUpperCase();
});
console.log(result); // HELLO
}
main();Тут callback має тип:
(value: string) => Promise<string>Callback може мати необов’язкові параметри:
type Formatter = (
value: string,
prefix?: string
) => string;
const format: Formatter = (value, prefix = "") => {
return `${prefix}${value}`;
};
console.log(format("TypeScript")); // TypeScript
console.log(format("TypeScript", "Мова: ")); // Мова: TypeScriptПараметр prefix має тип string | undefined, якщо для нього не вказано значення за замовчуванням. Значення за замовчуванням автоматично обробляє випадок undefined.
Нижче функція фільтрує та перетворює список користувачів. Обидва callback типобезпечні:
type User = {
id: number;
name: string;
active: boolean;
};
type UserFilter = (user: User) => boolean;
type UserMapper<TResult> = (user: User) => TResult;
function selectUsers(
users: User[],
filter: UserFilter
): User[] {
return users.filter(filter);
}
function mapUsers<TResult>(
users: User[],
mapper: UserMapper<TResult>
): TResult[] {
return users.map(mapper);
}
const users: User[] = [
{ id: 1, name: "Олена", active: true },
{ id: 2, name: "Андрій", active: false },
{ id: 3, name: "Марія", active: true },
];
const activeUsers = selectUsers(
users,
(user) => user.active
);
const userNames = mapUsers(
activeUsers,
(user) => user.name
);
console.log(activeUsers);
// [
// { id: 1, name: "Олена", active: true },
// { id: 3, name: "Марія", active: true }
// ]
console.log(userNames);
// ["Олена", "Марія"]У callback параметр user автоматично має тип User. TypeScript також перевіряє доступ до властивостей:
// Помилка: властивості email немає в типі User
// const emails = mapUsers(users, (user) => user.email);Неправильно:
function execute(callback: string): void {
// ...
}Тут callback описаний як рядок, а не функція.
Правильно:
function execute(callback: () => string): void {
const result = callback();
console.log(result);
}anyfunction run(callback: (value: any) => any): any {
return callback("value");
}any вимикає більшість перевірок TypeScript. Краще описати конкретні типи або використати дженерики:
function run<T, R>(
value: T,
callback: (value: T) => R
): R {
return callback(value);
}Неправильно:
type Handler = (value: string) => void;
// async-функція фактично повертає Promise<void>Для асинхронного callback потрібно:
type AsyncHandler = (value: string) => Promise<void>;Якщо зовнішня функція використовує результат callback, не слід вказувати void:
function calculate(
value: number,
callback: (value: number) => number
): number {
return callback(value) * 2;
}
const result = calculate(5, (value) => value + 1);
console.log(result); // 12Тут callback має повертати number, оскільки результат використовується в обчисленні.
Якщо callback передається безпосередньо у функцію з відомою сигнатурою, TypeScript сам визначить параметри:
const values = [1, 2, 3];
const doubled = values.map((value) => value * 2);Явно вказувати типи параметрів потрібно тоді, коли це покращує зрозумілість або callback зберігається окремо:
const double = (value: number): number => value * 2;
const doubled = values.map(double);Тип callback записують як (parameters) => ReturnType.
Callback як параметр описують безпосередньо в сигнатурі функції або через окремий тип.
void використовують, коли результат callback не потрібен.
Для асинхронного callback використовують Promise<ReturnType>.
Контекстна типізація дає змогу TypeScript автоматично визначати типи параметрів callback.
Дженерики допомагають пов’язати тип вхідного значення з параметрами та результатом callback.
Уникайте any, щоб зберегти перевірку типів.
Тип callback повинен відповідати параметрам, які передає зовнішня функція, і типу результату, який вона очікує.