Типизация параметров и возвращаемого значения
Синтаксис типизации функций, аннотации параметров, возвращаемые типы и вывод типов — основа для написания надёжного TypeScript-кода.
🎯 Зачем типизировать функции?
Функции — это строительные блоки любого JavaScript-приложения. Каждая функция принимает какие-то данные, обрабатывает их и возвращает результат. В JavaScript вы можете передать что угодно в что угодно — и узнать об ошибке только во время выполнения программы.
TypeScript позволяет описать заранее, какие типы данных ожидает функция и какой тип она возвращает. Это создаёт контракт: функция обязана работать только с указанными типами, а вызывающий код обязан передавать правильные аргументы.
💡 Аналогия: Представьте, что функция — это машина для производства сока. Вы кладёте апельсины (параметры), а получаете сок (результат). Типизация говорит: «Кладите только апельсины, а не камни — иначе машина сломается».
Без типизации:
// JavaScript — без каких-либо ограничений
function multiply(a, b) {
return a * b;
}
multiply(5, 3); // 15 — работает
multiply("5", 3); // "53" — баг! Строки конкатенируются
multiply(5); // undefined — баг! Второй аргумент пропущен
multiply(5, "hello"); // NaN — баг! Невозможно умножить
С типизацией:
// TypeScript — всё под контролем
function multiply(a: number, b: number): number {
return a * b;
}
multiply(5, 3); // 15 — работает
multiply("5", 3); // ❌ Ошибка компиляции: string не assignable к number
multiply(5); // ❌ Ошибка компиляции: пропущен 2-й аргумент
multiply(5, "hello"); // ❌ Ошибка компиляции: string не assignable к number
Видите, как TypeScript сразу ловит ошибки? Вы узнаёте о проблеме ещё до запуска программы, прямо в редакторе кода.
📝 Базовый синтаксис типизации функций
Типизация функции в TypeScript состоит из двух частей: типизация параметров и типизация возвращаемого значения. Давайте разберём каждую по отдельности.
Типизация параметров
Каждый параметр функции можно снабдить аннотацией типа. Для этого после имени параметра ставим двоеточие и указываем тип:
// Один параметр
function greet(name: string) {
console.log("Привет, " + name);
}
// Несколько параметров
function add(a: number, b: number) {
return a + b;
}
// Параметры разных типов
function createUser(name: string, age: number, isActive: boolean) {
return { name, age, isActive };
}
Обратите внимание: двоеточие ставится после имени параметра, а не перед ним. Это важный момент, который часто путают новички.
❌ Неправильно:
function greet(:string name) { } // Двоеточие перед именем — ошибка синтаксиса!
function greet(name:string) { } // Без пробела — работает, но стиль плохой
✅ Правильно:
function greet(name: string) { } // Двоеточие + пробел — идеально
Типизация возвращаемого значения
После закрывающей скобки параметров ставим двоеточие и указываем тип того, что функция возвращает:
// Функция возвращает число
function add(a: number, b: number): number {
return a + b;
}
// Функция возвращает строку
function formatName(firstName: string, lastName: string): string {
return firstName + " " + lastName;
}
// Функция возвращает булево значение
function isEven(num: number): boolean {
return num % 2 === 0;
}
// Функция возвращает объект
function createPoint(x: number, y: number): { x: number; y: number } {
return { x, y };
}
Полная сигнатура функции выглядит так:
function имя(параметр1: тип, параметр2: тип): возвращаемый_тип {
// тело функции
}
Давайте рассмотрим полный пример с несколькими функциями, чтобы закрепить:
// Простая функция сложения
function sum(a: number, b: number): number {
return a + b;
}
// Функция, проверяющая возраст
function isAdult(age: number): boolean {
return age >= 18;
}
// Функция, формирующая приветствие
function buildGreeting(name: string, timeOfDay: string): string {
return "Добрый " + timeOfDay + ", " + name + "!";
}
// Вызовы — всё работает корректно
console.log(sum(2, 3)); // 5
console.log(isAdult(20)); // true
console.log(isAdult(15)); // false
console.log(buildGreeting("Алексей", "день")); // Добрый день, Алексей!
🔍 Вывод типов (Type Inference)
TypeScript — умный язык. Он умеет выводить типы автоматически. Если вы указали тип параметра, TypeScript часто может сам определить тип возвращаемого значения.
// TypeScript сам выводит тип возвращаемого значения как number
function add(a: number, b: number) {
return a + b; // TS понимает: number + number = number
}
// TypeScript выводит string
function toUpperCase(text: string) {
return text.toUpperCase(); // toUpperCase() возвращает string
}
// TypeScript выводит boolean
function hasPermission(user: string, permission: string) {
return user === "admin"; // Сравнение возвращает boolean
}
Если навести курсор на функцию add в редакторе кода, вы увидите, что TypeScript показывает:
function add(a: number, b: number): number
Тип возвращаемого значения определён автоматически! Это называется вывод типов (type inference).
✅ Когда стоит полагаться на вывод типов: Если тип очевиден из тела функции — можно не указывать его явно. TypeScript справится сам.
⚠️ Когда нужно указывать тип явно: Если функция возвращает разные типы в зависимости от условий, или если вы хотите явно задокументировать контракт функции.
Пример: когда вывод типов работает не в вашу пользу
// TypeScript выводит "number", а не "number | string"
function getValue(input: string) {
if (input === "age") {
return 25; // number
}
return "unknown"; // string
}
// TypeScript считает, что функция возвращает number | string
// Это правильный вывод, но иногда вы хотите уточнить тип:
const result = getValue("age");
// result имеет тип: number | string
// Если вы попробуете вызвать result.toFixed(2):
result.toFixed(2); // ❌ Ошибка: Property 'toFixed' does not exist on type 'string'
В таких случаях лучше явно указать тип возвращаемого значения, чтобы код был более предсказуемым:
// Явное указание union-типа возвращаемого значения
function getValue(input: string): number | string {
if (input === "age") {
return 25;
}
return "unknown";
}
📭 Тип void: функции без возврата
Не все функции возвращают результат. Многие функции просто выполняют действие и завершаются. Для таких функций используется тип void.
// Функция ничего не возвращает — тип возврата: void
function logMessage(message: string): void {
console.log(message);
// Здесь нет return — функция ничего не возвращает
}
// Ещё пример — функция, которая изменяет интерфейс
function setUserName(user: { name: string }, newName: string): void {
user.name = newName; // Модифицирует объект, ничего не возвращает
}
// Функция с побочным эффектом
function playNotificationSound(): void {
const audio = new Audio("notification.mp3");
audio.play();
}
Вот что важно знать о void:
voidозначает, что функция не возвращает никакого значения- Вы не можете использовать результат такой функции:
const x = logMessage("hi"); // x будет undefined - Функция может содержать
return;без значения, но это не обязательно - Это не то же самое, что
undefined— хотя результат и будетundefined
void vs undefined
// void — это тип возвращаемого значения (намерение)
function doNothing(): void {
// Явно ничего не возвращаем
}
// Если бы функция возвращала undefined:
function doNothingExplicit(): undefined {
return undefined; // Это не одно и то же с void!
}
Тип void говорит: «эта функция не предназначена для использования её результата». Тип undefined говорит: «эта функция возвращает именно значение undefined».
🚫 Тип never: функции, которые не завершаются
Есть особый тип never, который означает, что функция никогда не завершается нормально. Это происходит в двух случаях:
// Случай 1: Функция выбрасывает исключение
function throwError(message: string): never {
throw new Error(message);
}
// Случай 2: Функция бесконечно работает
function infiniteLoop(): never {
while (true) {
// Бесконечный цикл — функция никогда не вернёт управление
}
}
// Случай 3: Функция с полным покрытием условий
function processValue(value: "yes" | "no"): string {
switch (value) {
case "yes":
return "Обработано";
case "no":
return "Отклонено";
default:
// Сюда никогда не попадём — все варианты covered
const exhaustiveCheck: never = value;
return exhaustiveCheck;
}
}
⚠️ Важно: Если функция имеет тип возврата never, вы никогда не сможете использовать её результат — функция просто не доходит до возврата значения.
Тип never отличается от void:
// void — функция завершается, но ничего не возвращает
function log(msg: string): void {
console.log(msg);
}
// never — функция никогда не завершается нормально
function crash(): never {
throw new Error("Критическая ошибка!");
}
На практике вы будете использовать void гораздо чаще, чем never. Но понимание never важно для продвинутых сценариев, особенно при работе с union-типами и exhaustiveness checking.
⚖️ Явная типизация vs вывод типов
Один из самых частых вопросов новичков: «Нужно ли всегда указывать тип возвращаемого значения явно?». Давайте разберём.
Когда TypeScript выводит тип автоматически
// TypeScript определяет, что возвращается number
function square(x: number) {
return x * x; // Тип: number
}
// TypeScript определяет, что возвращается string
function formatCurrency(amount: number) {
return "$" + amount.toFixed(2); // Тип: string
}
// TypeScript определяет, что возвращается boolean
function isPositive(num: number) {
return num > 0; // Тип: boolean
}
// TypeScript определяет, что возвращается string[]
function getNames() {
return ["Анна", "Борис", "Вика"]; // Тип: string[]
}
Когда стоит указывать тип явно
// 1. Когда тип неочевиден
function processData(data: unknown) {
if (typeof data === "string") {
return data.toUpperCase(); // string
}
return null; // TS выведет string | null — что может быть неожиданным
}
// Лучше указать явно:
function processData(data: unknown): string | null {
if (typeof data === "string") {
return data.toUpperCase();
}
return null;
}
// 2. Для документации — тип как подсказка другим разработчикам
function calculateDiscount(price: number, discountPercent: number): number {
return price * (1 - discountPercent / 100);
}
// 3. Для функций с побочными эффектами
function sendEmail(to: string, subject: string, body: string): void {
// Отправка письма — ничего не возвращаем
}
📌 Рекомендация
Всегда указывайте типы параметров явно. Тип возвращаемого значения можно опускать, когда TypeScript может вывести его однозначно. Но для публичных функций (экспортируемых из модуля) рекомендуется указывать оба типа явно — это улучшает читаемость и документированность кода.
🛠️ Практические примеры
Давайте рассмотрим несколько реальных примеров, где типизация функций делает код надёжнее и понятнее.
Пример 1: Валидация данных
// Функция проверки возраста
function validateAge(age: number): boolean {
if (age < 0 || age > 150) {
return false;
}
return true;
}
// Функция проверки email
function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// Функция проверки пароля
function checkPasswordStrength(password: string): "слабый" | "средний" | "сильный" {
if (password.length < 6) {
return "слабый";
}
if (password.length < 10 || !/[A-Z]/.test(password)) {
return "средний";
}
return "сильный";
}
// Использование
const age = 25;
if (validateAge(age)) {
console.log("Возраст корректный");
}
const email = "user@example.com";
if (isValidEmail(email)) {
console.log("Email валиден");
}
const strength = checkPasswordStrength("MyP@ssw0rd");
console.log(strength); // "сильный"
Пример 2: Работа с массивами
// Функция поиска в массиве
function findByName(names: string[], searchName: string): string | undefined {
return names.find(name => name === searchName);
}
// Функция подсчёта элементов
function countOccurrences(arr: number[], target: number): number {
return arr.filter(item => item === target).length;
}
// Функция группировки
function groupBy(items: T[], keyFn: (item: T) => string): Record {
const result: Record = {};
for (const item of items) {
const key = keyFn(item);
if (!result[key]) {
result[key] = [];
}
result[key].push(item);
}
return result;
}
// Использование
const names = ["Анна", "Борис", "Алексей", "Андрей"];
const found = findByName(names, "Борис");
console.log(found); // "Борис"
const numbers = [1, 2, 3, 2, 4, 2, 5];
console.log(countOccurrences(numbers, 2)); // 3
const people = [
{ name: "Анна", city: "Москва" },
{ name: "Борис", city: "Питер" },
{ name: "Вика", city: "Москва" }
];
const byCity = groupBy(people, p => p.city);
// { "Москва": [{ name: "Анна", ... }, { name: "Вика", ... }], "Питер": [...] }
Пример 3: Конвертация данных
// Функция конвертации температуры
function celsiusToFahrenheit(celsius: number): number {
return celsius * 9 / 5 + 32;
}
// Функция форматирования даты
function formatDate(date: Date): string {
const day = date.getDate().toString().padStart(2, "0");
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const year = date.getFullYear();
return day + "." + month + "." + year;
}
// Функция форматирования валюты
function formatCurrency(amount: number, currency: string = "₽"): string {
return amount.toLocaleString("ru-RU") + " " + currency;
}
// Использование
console.log(celsiusToFahrenheit(0)); // 32
console.log(celsiusToFahrenheit(100)); // 212
console.log(formatDate(new Date())); // "25.06.2025" (примерно)
console.log(formatCurrency(1500)); // "1 500 ₽"
console.log(formatCurrency(1500, "$")); // "1 500 $"
🔗 Типы возвращаемых значений: что ещё возможно
Помимо примитивных типов (number, string, boolean) и void, функции могут возвращать более сложные типы. Рассмотрим основные варианты:
Возвращаемые типы: массивы и объекты
// Возврат массива
function getNumbers(): number[] {
return [1, 2, 3, 4, 5];
}
// Возврат объекта
function getUser(): { name: string; age: number; email: string } {
return {
name: "Иван",
age: 30,
email: "ivan@example.com"
};
}
// Возврат кортежа
function getMinMax(numbers: number[]): [number, number] {
const sorted = [...numbers].sort((a, b) => a - b);
return [sorted[0], sorted[sorted.length - 1]];
}
// Возврат union-типа
function getResponse(status: "success" | "error"): { status: string; data?: string } {
if (status === "success") {
return { status: "ok", data: "Данные загружены" };
}
return { status: "error" };
}
// Использование
const nums = getNumbers();
console.log(nums.length); // 5
const user = getUser();
console.log(user.name); // "Иван"
const [min, max] = getMinMax([3, 1, 4, 1, 5, 9, 2, 6]);
console.log(min, max); // 1 9
const response = getResponse("success");
if (response.data) {
console.log(response.data); // "Данные загружены"
}
Типизация объектов в возвращаемом значении
// Определяем тип объекта через type alias
type Point = {
x: number;
y: number;
};
type Rectangle = {
topLeft: Point;
bottomRight: Point;
};
// Функция, возвращающая типизированный объект
function createRectangle(x1: number, y1: number, x2: number, y2: number): Rectangle {
return {
topLeft: { x: x1, y: y1 },
bottomRight: { x: x2, y: y2 }
};
}
// Функция, вычисляющая площадь прямоугольника
function calculateArea(rect: Rectangle): number {
const width = Math.abs(rect.bottomRight.x - rect.topLeft.x);
const height = Math.abs(rect.bottomRight.y - rect.topLeft.y);
return width * height;
}
const rect = createRectangle(0, 0, 10, 5);
console.log(calculateArea(rect)); // 50
❌ Частые ошибки при типизации функций
Давайте разберём типичные ошибки, которые допускают начинающие TypeScript-разработчики при работе с типизацией функций.
Ошибка 1: Неправильный синтаксис
// ❌ Неправильно — двоеточие перед именем параметра
function greet(: string name) { }
// ❌ Неправильно — тип после имени без двоеточия
function greet(name string) { }
// ✅ Правильно
function greet(name: string): string {
return "Привет, " + name;
}
Ошибка 2: Возврат неправильного типа
// ❌ Ошибка: функция объявлена как возвращающая number, но возвращает string
function getNumber(): number {
return "42"; // Ошибка: string не assignable к number
}
// ❌ Ошибка: функция объявлена как void, но пытается вернуть значение
function doSomething(): void {
return 42; // Ошибка: A 'void' function must not return a value
}
// ✅ Правильно
function getNumber(): number {
return 42;
}
function doSomething(): void {
console.log("Действие выполнено");
// return не нужен, или return; без значения
}
Ошибка 3: Забыли указать тип параметра
// ⚠️ Без аннотации типа TypeScript выведет тип как any
function addItem(item) {
// item имеет тип any — это опасно!
return item.name; // Если передать число — получим ошибку в рантайме
}
// ✅ Правильно — всегда указывайте типы параметров
function addItem(item: { name: string; price: number }): { name: string; price: number; id: number } {
return { ...item, id: Date.now() };
}
Ошибка 4: Несоответствие типов при вызове
function multiply(a: number, b: number): number {
return a * b;
}
// ❌ Ошибка: передаём строку вместо числа
multiply("5", 3); // Ошибка компиляции
// ❌ Ошибка: пропускаем аргумент
multiply(5); // Ошибка компиляции: Expected 2 arguments, but got 1
// ❌ Ошибка: передаём лишний аргумент
multiply(5, 3, 10); // Ошибка компиляции: Expected 2 arguments, but got 3
// ✅ Правильно
multiply(5, 3); // 15
💡 Практические советы
Вот несколько рекомендаций, которые помогут вам писать лучше типизированные функции:
Типы параметров — это основа. Вывод типов хорош для возвращаемого значения, но для параметров всегда пишите тип явно.
Если функция ничего не возвращает — явно укажите void. Это подчёркивает намерение и предотвращает использование результата.
Если TypeScript может вывести тип — он сделает это правильно. Не дублируйте информацию без необходимости.
Имя функции должно говорить о том, что она делает. Хорошие имена заменяют комментарии: calculateTotal лучше calc.
📋 Итоги урока
Давайте подведём итоги того, что мы изучили в этом уроке:
- Типизация параметров — после имени параметра ставим двоеточие и тип:
name: string - Типизация возвращаемого значения — после скобок параметров:
: number - Вывод типов — TypeScript может автоматически определить тип возвращаемого значения
- Тип void — функция ничего не возвращает (побочные эффекты)
- Тип never — функция никогда не завершается нормально (ошибка, бесконечный цикл)
- Явная vs неявная типизация — параметры типизируем всегда, возвращаемое значение — по ситуации
✅ Ключевое правило: Типизация функций — это контракт между функцией и её вызывающим кодом. Параметры — это то, что функция ожидает получить. Возвращаемое значение — это то, что она обещает вернуть.
В следующем уроке мы разберём необязательные параметры и значения по умолчанию — ещё один важный аспект типизации функций.
Проверяем понимание
6 вопросов