Функції в TypeScript
TypeScript функції — вендінговий автомат з різними слотами для монет та вікнами з продуктами
Функції -- основа будь-якої програми. У JavaScript ми вже навчились писати різні типи функцій (Block 4). Тепер дізнаємось, як TypeScript допомагає писати функції безпечніше: типізація параметрів, return types, function types та overloading.
Типізація параметрів та return
У TypeScript кожен параметр та значення, що повертається, може мати тип:
// Явна типізація параметрів та return
function add(a: number, b: number): number {
return a + b;
}
// Arrow function
const multiply = (a: number, b: number): number => a * b;
// TypeScript перевіряє виклики
add(2, 3); // OK: 5
// add("2", "3"); // Помилка!
// add(2); // Помилка: очікується 2 аргументи
Return type inference
TypeScript часто може визначити return type автоматично:
// TypeScript сам визначає, що return type -- number
function add(a: number, b: number) {
return a + b;
}
// Але явний return type корисний для документації та захисту
function divide(a: number, b: number): number {
// return "результат"; // Помилка: Type 'string' is not assignable to type 'number'
return a / b;
}
Рекомендація: для простих функцій покладайся на type inference. Для публічних API, функцій у бібліотеках або складних функцій -- завжди вказуй return type явно. Це захищає від випадкової зміни типу повернення.
void та never
void -- функція нічого не повертає
function logMessage(message: string): void {
console.log(message);
// return undefined; -- OK (void дозволяє return без значення)
}
function showAlert(text: string): void {
alert(text);
}
// void vs undefined
// void означає "ігноруй значення, що повертається"
// undefined означає "функція повертає саме undefined"
function returnVoid(): void {} // OK
function returnUndef(): undefined {
return undefined; // Потрібен явний return undefined
}
never -- функція ніколи не завершується
// Кидає помилку -- не дійде до кінця функції
function throwError(message: string): never {
throw new Error(message);
}
// Нескінченний цикл
function watchChanges(): never {
while (true) {
// слухаємо зміни...
}
}
// never корисний для exhaustive checks (урок 7.2)
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
Optional та Default параметри
Optional параметри
// ? робить параметр необов'язковим (string | undefined)
function createUser(name: string, email: string, age?: number): string {
if (age !== undefined) {
return `${name} (${email}), вік: ${age}`;
}
return `${name} (${email})`;
}
createUser("Олексій", "alex@test.com"); // OK
createUser("Олексій", "alex@test.com", 25); // OK
Optional параметри завжди повинні бути останніми у списку. Не можна написати function f(a?: string, b: number) -- TypeScript покаже помилку.
Default параметри
// Default значення також визначає тип
function greet(name: string, greeting: string = "Привіт"): string {
return `${greeting}, ${name}!`;
}
greet("Олексій"); // "Привіт, Олексій!"
greet("Олексій", "Вітаю"); // "Вітаю, Олексій!"
// Default може бути виразом
function createId(prefix: string = "id", timestamp: number = Date.now()): string {
return `${prefix}-${timestamp}`;
}
Rest параметри
// Rest params -- масив аргументів
function sum(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(10, 20, 30, 40)); // 100
// Комбінація з обов'язковими параметрами
function log(level: "info" | "warn" | "error", ...messages: string[]): void {
console.log(`[${level.toUpperCase()}]`, ...messages);
}
log("info", "Сервер запущено");
log("error", "Не вдалося", "підключитися", "до бази");
Function Types -- типи функцій
Ми можемо описати тип функції окремо і використовувати його:
// Type alias для функції
type MathOperation = (a: number, b: number) => number;
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
const multiply: MathOperation = (a, b) => a * b;
// Функція, що приймає функцію (callback)
function calculate(a: number, b: number, operation: MathOperation): number {
return operation(a, b);
}
console.log(calculate(10, 5, add)); // 15
console.log(calculate(10, 5, subtract)); // 5
console.log(calculate(10, 5, multiply)); // 50
Callback types
// Тип для callback
type EventCallback = (event: { type: string; target: string }) => void;
function on(eventName: string, callback: EventCallback): void {
// реєструємо обробник
callback({ type: eventName, target: "button" });
}
on("click", (event) => {
console.log(`Подія: ${event.type} на ${event.target}`);
});
// Тип для фільтра
type FilterFn<T> = (item: T) => boolean;
function filterArray<T>(items: T[], predicate: FilterFn<T>): T[] {
return items.filter(predicate);
}
Interface для функцій
// Іноді interface використовують для callable objects
interface Formatter {
(value: string): string;
locale: string;
}
// Це рідкісний патерн, зазвичай type alias зручніший
Function Overloading -- перевантаження функцій
Одна функція може мати різні сигнатури для різних типів аргументів:
// Overload signatures (оголошення)
function format(value: string): string;
function format(value: number): string;
function format(value: Date): string;
// Implementation signature (реалізація)
function format(value: string | number | Date): string {
if (typeof value === "string") {
return value.trim();
}
if (typeof value === "number") {
return value.toFixed(2);
}
return value.toLocaleDateString("uk-UA");
}
// TypeScript знає конкретний результат для кожного виклику
format(" Привіт "); // string
format(3.14159); // string
format(new Date()); // string
Практичний приклад
// createElement з різними параметрами
function createElement(tag: "div"): HTMLDivElement;
function createElement(tag: "span"): HTMLSpanElement;
function createElement(tag: "input"): HTMLInputElement;
function createElement(tag: string): HTMLElement;
function createElement(tag: string): HTMLElement {
return document.createElement(tag);
}
const div = createElement("div"); // HTMLDivElement
const input = createElement("input"); // HTMLInputElement
const custom = createElement("section"); // HTMLElement
Function overloading корисний, але не зловживай ним. Часто union types або generics (наступний урок) -- простіше рішення. Overloading найкращий, коли return type залежить від аргументів.
this у TypeScript
TypeScript дозволяє типізувати this:
interface User {
name: string;
greet(this: User): string;
}
const user: User = {
name: "Олексій",
greet() {
return `Привіт, я ${this.name}!`; // this: User
},
};
user.greet(); // OK
// const greetFn = user.greet;
// greetFn(); // Помилка: The 'this' context... is not assignable
Практика: калькулятор з типізацією
type Operation = "add" | "subtract" | "multiply" | "divide";
type CalcResult = {
operation: Operation;
operands: [number, number];
result: number;
};
function calculate(a: number, b: number, op: Operation): CalcResult {
let result: number;
switch (op) {
case "add":
result = a + b;
break;
case "subtract":
result = a - b;
break;
case "multiply":
result = a * b;
break;
case "divide":
if (b === 0) {
throw new Error("Ділення на нуль!");
}
result = a / b;
break;
}
return { operation: op, operands: [a, b], result };
}
function formatResult(calcResult: CalcResult): string {
const symbols: Record<Operation, string> = {
add: "+",
subtract: "-",
multiply: "*",
divide: "/",
};
const { operands, operation, result } = calcResult;
return `${operands[0]} ${symbols[operation]} ${operands[1]} = ${result}`;
}
// Використання
const result = calculate(10, 3, "add");
console.log(formatResult(result)); // "10 + 3 = 13"
// calculate(10, 3, "power"); // Помилка: "power" не входить в Operation
Підсумок
- Типізуй параметри і return type функцій
- void -- функція нічого не повертає; never -- функція ніколи не завершується
- Optional (
?) та default параметри працюють як у JS, але з типами - Rest параметри типізуються як масив:
...args: number[] - Function types (
type Fn = (a: number) => string) -- описують сигнатуру функції - Overloading -- різні сигнатури для різних типів аргументів
- TypeScript може виводити return type автоматично (type inference)
Що далі?
У наступному уроці ми дізнаємось про Generics -- один з найпотужніших інструментів TypeScript. Generics дозволяють створювати функції та типи, які працюють з будь-яким типом, зберігаючи при цьому type safety.
Корисні посилання:
- TypeScript Handbook: More on Functions -- повний гайд по функціях
- TypeScript Handbook: Function Overloads -- деталі overloading
- Total TypeScript: Function Types -- практичні поради
- MDN: Arrow functions -- нагадування про arrow functions