Глава 4. Функции: типы параметров и возвращаемое значение

210 просмотров
0 лайков
0 в избранном

Аннотация функции

У параметров и возвращаемого значения указываются типы:

function add(a: number, b: number): number {
    return a + b;
}

const result: number = add(3, 4); // 7

Если функция ничего не возвращает — тип void:

function log(message: string): void {
    console.log(message);
}

Выведение возвращаемого типа

TypeScript может сам вывести возвращаемый тип, но для публичных функций лучше указывать его явно — это часть контракта и защита от случайного возврата не того значения:

// Лучше явно
function isAdmin(user: User): boolean {
    return user.role === "admin";
}

// Плохо: случайно вернём undefined, и TypeScript это разрешит
function isAdmin(user: User) {
    if (user.role === "admin") {
        return true;
    }
    // забыли return false — тип станет boolean | undefined
}

Необязательные параметры и значения по умолчанию

function greet(name: string, greeting: string = "Привет"): string {
    return `${greeting}, ${name}!`;
}

greet("Анна");               // Привет, Анна!
greet("Анна", "Здравствуй"); // Здравствуй, Анна!

Необязательный параметр (без значения по умолчанию) помечается ? и должен идти после обязательных:

function log(message: string, level?: string): void {
    console.log(`[${level ?? "INFO"}] ${message}`);
}

log("Запущено");        // [INFO] Запущено
log("Ошибка", "ERROR"); // [ERROR] Ошибка

Остаточные параметры (rest)

Когда аргументов может быть сколько угодно — ...args:

function sumAll(...numbers: number[]): number {
    return numbers.reduce((acc, n) => acc + n, 0);
}

sumAll(1, 2, 3, 4); // 10
sumAll();           // 0

Реальный кейс — логирование с тегами:

function debug(tag: string, ...args: unknown[]): void {
    console.debug(`[${tag}]`, ...args);
}

debug("api", "запрос", { url: "/users", method: "GET" });

Стрелочные функции и тип функции как значение

Стрелочные функции типизируются так же:

const multiply = (a: number, b: number): number => a * b;

Тип функции можно вынести в alias и переиспользовать:

type MathOp = (a: number, b: number) => number;

const add: MathOp = (a, b) => a + b;       // типы a и b выводятся
const divide: MathOp = (a, b) => a / b;
const modulo: MathOp = (a, b) => a % b;

function apply(op: MathOp, x: number, y: number): number {
    return op(x, y);
}

apply(add, 10, 3);    // 13
apply(divide, 10, 3); // 3.33

Кейс: тип колбэка в событийном API

type ClickHandler = (event: { target: HTMLElement; x: number; y: number }) => void;

function onClick(handler: ClickHandler): void {
    // ...
}

onClick((e) => console.log(e.target, e.x, e.y));

Перегрузка функций

Когда функция возвращает разные типы в зависимости от аргументов, описывают несколько сигнатур. Реализация — одна, с объединением типов:

function format(value: number): string;
function format(value: string): string;
function format(value: number | string): string {
    return typeof value === "number"
        ? value.toFixed(2)
        : value.toUpperCase();
}

format(3.1415); // "3.14" — TS знает, что вернётся string
format("hi");   // "HI"
// format(true); // Ошибка: нет подходящей сигнатуры

Реальный кейс: перегрузка для API-клиента

declare function request(path: string): Promise<unknown>;

function fetchUser(id: number): Promise<User>;
function fetchUser(ids: number[]): Promise<User[]>;
function fetchUser(idOrIds: number | number[]): Promise<User | User[]> {
    if (Array.isArray(idOrIds)) {
        return request("/users?id=" + idOrIds.join(",")) as Promise<User[]>;
    }
    return request("/users/" + idOrIds) as Promise<User>;
}

const one = fetchUser(1);    // Promise<User>
const many = fetchUser([1, 2]); // Promise<User[]>

Async-функции

Возвращаемое значение async-функции оборачивается в Promise<T>:

async function fetchUser(id: number): Promise<User> {
    const res = await fetch(`/api/users/${id}`);
    return res.json();
}

async function fetchAll(): Promise<User[]> {
    const [a, b] = await Promise.all([fetchUser(1), fetchUser(2)]);
    return [a, b];
}

Типизация ошибок

TypeScript не типизирует исключения — в catch всегда unknown (при useUnknownInCatchVariables):

try {
    await fetchUser(1);
} catch (err) {
    // err: unknown
    if (err instanceof Error) {
        console.log(err.message);
    }
}

this в функциях

В обычных функциях тип this можно указать первым параметром (он не передаётся в рантайме, нужен только компилятору):

interface Button {
    el: HTMLElement;
    label: string;
    render(this: Button): void;
}

const btn: Button = {
    el: document.createElement("button"),
    label: "OK",
    render() {
        this.el.textContent = this.label; // TS знает, что такое this
    },
};

Кейс: типобезопасный debounce

Утилита debounce с сохранением типов исходной функции — классический пример применения дженериков (подробнее в главе 6), но даже без них видна польза типизации колбэков:

function debounce<T extends (...args: any[]) => void>(
    fn: T,
    delay: number,
): (...args: Parameters<T>) => void {
    let timer: ReturnType<typeof setTimeout>;
    return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => fn(...args), delay);
    };
}

const onSearch = debounce((query: string) => {
    console.log("Ищу:", query);
}, 300);

onSearch("Type");     // OK
// onSearch(42);       // Ошибка: ожидается string

Типичные ошибки

1. Не тот порядок необязательных параметров

function f(a: string, b?: number, c: string): void {}
//                                ^^^^^^^^
// Ошибка: обязательный параметр после необязательного

2. Забыли вернуть значение

function isAdmin(user: User): boolean {
    if (user.role === "admin") {
        return true;
    }
    // забыли return — TypeScript с явным типом подсветит
}

3. Тип Function вместо конкретной сигнатуры

// Плохо: любая функция подойдёт
function onClick(handler: Function): void { ... }

// Хорошо: конкретная сигнатура
function onClick(handler: () => void): void { ... }

Практика

  1. Напишите функцию isAdult(age: number): boolean с явным возвратом.
  2. Сделайте стрелочную функцию square с типом (n: number) => number.
  3. Реализуйте функцию clamp(value, min, max) с параметрами по умолчанию для min и max.
  4. Опишите перегруженную функцию parseInput(value: string), которая возвращает number, если строка — число, иначе string.
  5. Напишите async-функцию, которая параллельно загружает двух пользователей и возвращает массив.

Итог

Мы разобрали типизацию параметров и возвращаемых значений, параметры по умолчанию и rest-параметры, функциональные типы, перегрузку и работу с async/await. В следующей главе — классы и модификаторы доступа.

Комментарии 0

Для добавления комментариев необходимо войти или зарегистрироваться.

Пока нет комментариев. Станьте первым!