Вивчай

Функції в TypeScript

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.

Інфо

Корисні посилання: