$ sudo teach IT
МОДУЛЬ 4 · УРОК 1

Типизация параметров и возвращаемого значения

Синтаксис типизации функций, аннотации параметров, возвращаемые типы и вывод типов — основа для написания надёжного TypeScript-кода.

⏱ ~20 минут 🎓 Базовый уровень 📌 Функции

🎯 Зачем типизировать функции?

Функции — это строительные блоки любого 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

💡 Практические советы

Вот несколько рекомендаций, которые помогут вам писать лучше типизированные функции:

1️⃣
Типизируйте параметры всегда

Типы параметров — это основа. Вывод типов хорош для возвращаемого значения, но для параметров всегда пишите тип явно.

2️⃣
Используйте void для побочных эффектов

Если функция ничего не возвращает — явно укажите void. Это подчёркивает намерение и предотвращает использование результата.

3️⃣
Доверяйте выводу типов

Если TypeScript может вывести тип — он сделает это правильно. Не дублируйте информацию без необходимости.

4️⃣
Пишите осмысленные имена

Имя функции должно говорить о том, что она делает. Хорошие имена заменяют комментарии: calculateTotal лучше calc.

📋 Итоги урока

Давайте подведём итоги того, что мы изучили в этом уроке:

  • Типизация параметров — после имени параметра ставим двоеточие и тип: name: string
  • Типизация возвращаемого значения — после скобок параметров: : number
  • Вывод типов — TypeScript может автоматически определить тип возвращаемого значения
  • Тип void — функция ничего не возвращает (побочные эффекты)
  • Тип never — функция никогда не завершается нормально (ошибка, бесконечный цикл)
  • Явная vs неявная типизация — параметры типизируем всегда, возвращаемое значение — по ситуации

✅ Ключевое правило: Типизация функций — это контракт между функцией и её вызывающим кодом. Параметры — это то, что функция ожидает получить. Возвращаемое значение — это то, что она обещает вернуть.

В следующем уроке мы разберём необязательные параметры и значения по умолчанию — ещё один важный аспект типизации функций.

Проверяем понимание

6 вопросов

Функция с типизированными параметрами

Premium