Функции в TypeScript: параметры, возвращаемые значения, void, never и overloads
Функции мы пишем постоянно.
Но TypeScript позволяет описать их гораздо точнее, чем обычный JavaScript.
Начнём с простого.
Типизация параметров
В JavaScript:
function sum(a, b) {
return a + b;
}
JavaScript спокойно позволит:
sum(1, 2);
sum("hello", "world");
sum({}, []);
Для JavaScript это нормально.
Для TypeScript можно явно указать типы:
function sum(a: number, b: number) {
return a + b;
}
Теперь:
sum(1, 2);
работает.
А:
sum("hello", "world");
даст ошибку.
Тип возвращаемого значения
TypeScript часто сам понимает, что возвращает функция:
function sum(a: number, b: number) {
return a + b;
}
Он выводит:
number
Но иногда тип лучше указать явно:
function sum(
a: number,
b: number
): number {
return a + b;
}
Особенно это полезно для публичных функций, сложной логики и API.
Например:
function getUser(id: number): User | null {
// ...
}
Сразу понятно:
функция либо возвращает User, либо ничего не находит и возвращает null
Необязательные параметры
Допустим:
function greet(name: string, age?: number) {
// ...
}
Теперь можно вызвать:
greet("Tigran");
или:
greet("Tigran", 20);
Но внутри функции TypeScript понимает:
age
может быть:
number | undefined
Поэтому:
console.log(age.toFixed());
может вызвать ошибку.
Нужно сначала проверить:
if (age !== undefined) {
console.log(age.toFixed());
}
Что такое void?
void обычно означает:
Функция не возвращает полезного значения.
Например:
function logMessage(message: string): void {
console.log(message);
}
Функция что-то делает:
logMessage("Hello");
Но результат использовать нельзя:
const result = logMessage("Hello");
result будет иметь тип:
void
void часто встречается в callback-функциях.
Например:
button.addEventListener(
"click",
() => {
console.log("Clicked");
}
);
Нам важен сам факт выполнения функции.
Возвращаемое значение не нужно.
Что такое never?
never означает:
Эта функция никогда не завершится нормально.
Например:
function throwError(message: string): never {
throw new Error(message);
}
Функция либо выбрасывает ошибку:
throwError("Something went wrong");
либо может бесконечно выполняться:
function infiniteLoop(): never {
while (true) {
// ...
}
}
Разница между void и never:
void ↓ Функция завершилась, но ничего полезного не вернула. never ↓ Функция вообще не дошла до нормального завершения.
Например:
function log(): void {
console.log("Hello");
}
После:
log();
console.log("This will run");
вторая строка выполнится.
А:
function fail(): never {
throw new Error();
}
После:
fail();
console.log("This will run");
TypeScript понимает, что до второй строки выполнение не дойдёт.
Function overloads
Теперь немного интереснее.
Представим функцию:
function getUser(id: number): User {
// ...
}
Но мы хотим поддерживать два способа вызова:
getUser(1);
и:
getUser("Tigran");
Причём результат может отличаться.
Например:
getUser(1);
// User
getUser("Tigran");
// User[]
Можно написать overloads:
function getUser(id: number): User;
function getUser(name: string): User[];
function getUser(
value: number | string
): User | User[] {
if (typeof value === "number") {
return {
id: value,
name: "Tigran"
};
}
return [
{
id: 1,
name: value
}
];
}
Теперь TypeScript понимает:
const user = getUser(1);
Здесь:
user
имеет тип:
User
А здесь:
const users = getUser("Tigran");
тип:
User[]
Зачем overloads, если есть union?
Можно было написать:
function getUser(
value: number | string
): User | User[] {
// ...
}
Но тогда:
const result = getUser(1);
будет иметь тип:
User | User[]
Хотя мы точно знаем:
если передали number → получим User если передали string → получим User[]
Overloads позволяют TypeScript сохранить эту зависимость.
Когда overloads не нужны
Не стоит использовать overloads просто потому, что они существуют.
Иногда достаточно:
function print(value: string | number) {
console.log(value);
}
Если логика простая, union type обычно лучше.
Overloads полезны, когда:
тип аргумента ↓ влияет на ↓ тип результата
Итого
Функция в TypeScript может описывать гораздо больше, чем просто:
function sum(a, b)
Мы можем указать:
function sum(
a: number,
b: number
): number
Сделать параметр необязательным:
age?: number
Сказать, что функция ничего полезного не возвращает:
void
Или вообще никогда нормально не завершится:
never
А если результат зависит от переданных аргументов, можно использовать:
function overloads
И именно в этом заключается одна из главных идей TypeScript.
Мы описываем не только какие данные существуют, но и как наш код может с ними взаимодействовать.
TypeScript постепенно превращается из «JavaScript с типами» в систему, которая описывает правила и контракты всего приложения.