$ sudo teach IT
МОДУЛЬ 3 · УРОК 6

Type aliases — собственные имена типов

Научимся создавать собственные имена для типов и использовать union-типы для гибкой типизации переменных и функций

~25 минут Для новичков
Цель урока: Научиться создавать собственные имена для типов с помощью ключевого слова type, понять, что такое union-типы, и использовать их для написания чистого и понятного кода.
Предварительные знания: Базовое понимание примитивных типов (string, number, boolean), массивов и функций в TypeScript. Если вы прошли предыдущие уроки модуля 3 — вы полностью готовы!

1. Что такое type alias — аналогия с ярлыком

Представьте, что у вас на рабочем столе лежит множество файлов. Каждый файл содержит важную информацию: один — с данными о клиенте, другой — о продукте, третий — о заказе. Каждый раз, когда вам нужно обратиться к какому-то типу данных, выненев указывать его полностью — перечислять все поля, все типы, все свойства. Это утомительно и неудобно.

Теперь представьте, что вы можете создать ярлык — короткое, понятное имя, которое ссылается на целый набор данных. Вместо того чтобы каждый раз описывать сложную структуру, вы просто используете этот ярлык. Вот именно это и делает type alias в TypeScript!

Type alias (псевдоним типа) — это возможность создать собственное имя для любого типа в TypeScript. Это как дать короткое и понятное имя сложной структуре данных. Вы создаёте имя один раз, а потом используете его сколько угодно раз в коде.

Аналогия из жизни: Попросите друга: «Передай мнетот красный с16GB оперативной памяти, 512GB SSD и процессором M2 MacBook Pro». Или скажите: «Передай мне тот MacBook». Второй вариант гораздо короче и понятнее — именно это и делает type alias!

Без type alias вам пришлось бы каждый раз перечислять все свойства объекта. С type alias вы даёте структуре короткое имя и используете его везде. Это делает код чище, читабельнее и легче для сопровождения.

Type alias работает не только с объектами — вы можете давать имена любым типам: примитивам, массивам, функциям, union-типам и даже сложным вложенным структурам. Это один из самых универсальных инструментов TypeScript для организации типов.

2. Синтаксис: type Name = Type

Синтаксис type alias невероятно прост. Вы используете ключевое слово type, за которым следует имя (по конвенции — с большой буквы в PascalCase), знак равенства, а затем сам тип:

type ИмяТипа = Тип;

Вот базовый пример — создаём alias для простого string:

type UserName = string;

const name1: UserName = "Алексей";
const name2: UserName = "Мария";

В этом примере мы создали type alias с именем UserName, который является псевдонимом для типа string. Теперь вместо того чтобы каждый раз писать string, мы можем использовать более выразительное имя UserName.

Правило именования: Имена type alias всегда начинаются с заглавной буквы (PascalCase). Это не просто рекомендация — это требование TypeScript! Если вы напишете type userName = string — TypeScript покажет ошибку, потому что имена начинающиеся с маленькой буквы зарезервированы для переменных и параметров функций.

Важно понимать: type alias — это только псевдоним. TypeScript не создаёт новый тип — он просто даёт уже существующему типу дополнительное имя. В результатах компиляции type alias полностью исчезает, заменяясь исходным типом. Это чисто «синтаксический сахар» для удобства разработчика.

Теперь давайте рассмотрим, какие типы можно обернуть в type alias, и разберём каждый случай подробно.

3. Type alias для примитивов

Вы можете создавать type alias для любого примитивного типа: string, number, boolean, null, undefined. Это особенно полезно, когда вы хотите подчеркнуть семантику переменной.

Возьмём типичную ситуацию: у вас есть переменные, которые хранят идентификаторы. Без type alias код выглядит так:

const userId: string = "user_123";
const orderId: string = "order_456";
const productId: string = "product_789";
const sessionId: string = "session_000";

Видите проблему? Все переменные имеют тип string, но это «сырой» тип, который ничего не говорит о назначении переменных. А теперь посмотрите, как улучшается код с type alias:

type UserId = string;
type OrderId = string;
type ProductId = string;
type SessionId = string;

const userId: UserId = "user_123";
const orderId: OrderId = "order_456";
const productId: ProductId = "product_789";
const sessionId: SessionId = "session_000";

Теперь, глядя на код, сразу понятно, какое назначение у каждой переменной. UserId, OrderId, ProductId — каждое имя рассказывает о своёмназначение.

Примеры для других примитивов:

type Age = number;
type IsAdmin = boolean;
type Temperature = number;
type Email = string;
type Rating = number;
type IsActive = boolean;

const userAge: Age = 25;
const adminFlag: IsAdmin = true;
const roomTemp: Temperature = 22.5;
const userEmail: Email = "user@example.com";

Обратите внимание:хотя Age, Temperature и Rating все являются number, TypeScript всё равно позволяет присваивать значения одного типа другому. Type alias — это просто имя, а не новый тип.

Важно: Type alias для примитивов не добавляет никаких ограничений — это просто альтернативное имя. TypeScript не запрещает присваивать значение типа Age переменной типа Temperature, потому что оба они — number. Но в коде будет сразу видно, что вы делаете что-то странное.

Пример, где type alias для примитивов становится по-настоящему полезным:

type Celsius = number;
type Fahrenheit = number;

function celsiusToFahrenheit(temp: Celsius): Fahrenheit {
  return (temp * 9 / 5) + 32;
}

function fahrenheitToCelsius(temp: Fahrenheit): Celsius {
  return (temp - 32) * 5 / 9;
}

const boilingPoint: Celsius = 100;
const boilingInF = celsiusToFahrenheit(boilingPoint);
console.log(`Кипение: ${boilingPoint}°C = ${boilingInF}°F`);

Теперь, глядя на функцию celsiusToFahrenheit, сразу видно: принимает она температуру в Цельсиях и возвращает в Фаренгейтах. Это гораздо понятнее, чем если бы оба аргумента были number.

4. Type alias для объектов

Одно из самых частых применений type alias — создание имён для объектных типов. Здесь type alias раскрывается во всей красе, потому что позволяет избавиться от повторяющихся структур и сделать код значительно чище.

Представьте, что вы работаете над приложением для интернет-магазина и у вас есть множество мест, где используются данные о пользователе:

function createUser(name: string, email: string, age: number) {
  return { name, email, age };
}

function sendWelcomeEmail(user: { name: string; email: string; age: number }) {
  return `Привет, ${user.name}!`;
}

function validateUser(user: { name: string; email: string; age: number }) {
  return user.name.length > 0;
}

Видите? Один и тот же объектный тип { name: string; email: string; age: number } повторяется дважды. Это нарушает принцип DRY (Don't Repeat Yourself). Давайте исправим это с помощью type alias:

type User = {
  name: string;
  email: string;
  age: number;
};

function createUser(name: string, email: string, age: number): User {
  return { name, email, age };
}

function sendWelcomeEmail(user: User): string {
  return `Привет, ${user.name}!`;
}

function validateUser(user: User): boolean {
  return user.name.length > 0;
}

Отлично! Теперь структура определена один раз, а используется везде. Если вам понадобится добавить новое свойство (например, phone), вы добавите его только в одно место — в определение User.

Теперь давайте рассмотрим более сложные объектные структуры:

type Product = {
  id: number;
  name: string;
  price: number;
  description: string;
  inStock: boolean;
};

type Order = {
  orderId: string;
  customerName: string;
  items: Product[];
  totalAmount: number;
  isPaid: boolean;
};

function calculateDiscount(order: Order): number {
  if (order.items.length > 5) {
    return order.totalAmount * 0.1;
  }
  return 0;
}

Здесь Order ссылается на Product в свойстве items. Это читается почти как обычное предложение: «Заказ содержит массив продуктов».

Также можно создавать type alias с опциональными свойствами (через ?):

type UserProfile = {
  name: string;
  email: string;
  age?: number;           // опциональное
  phone?: string;         // опциональное
  address?: string;       // опциональное
  avatar?: string;        // опциональное
};

const profile1: UserProfile = {
  name: "Иван",
  email: "ivan@mail.com"
};

const profile2: UserProfile = {
  name: "Мария",
  email: "maria@mail.com",
  age: 28,
  phone: "+7 999 123 4567",
  avatar: "https://example.com/avatar.jpg"
};

Оба объекта соответствуют типу UserProfile, хотя у profile1 заполнены только обязательные поля.

5. Type alias для массивов

TypeScript поддерживает два способа создания типов для массивов, и оба можно использовать в type alias:

Способ 1: Синтаксис с квадратными скобками Type[]

Способ 2: Синтаксис с Array Array<Type>

Оба способа абсолютно эквивалентны, но большинство TypeScript-разработчиков предпочитают первый вариант за его краткость.

// Способ 1: синтаксис с квадратными скобками
type StringArray = string[];
type NumberArray = number[];
type BooleanArray = boolean[];

// Способ 2: синтаксис с Array
type StringArray2 = Array<string>;
type NumberArray2 = Array<number>;

// Использование
const names: StringArray = ["Аня", "Борис", "Вика"];
const scores: NumberArray = [95, 87, 92, 78];
const flags: BooleanArray = [true, false, true];

Теперь давайте создадим более практичные type alias для массивов:

type UserName = string;
type UserList = UserName[];

type Score = number;
type ScoreBoard = Score[];

const team: UserList = ["Алексей", "Борис", "Виктор"];
const results: ScoreBoard = [100, 85, 92, 78, 95];

А вот пример, где type alias для массива становится особенно полезным — когда вы работаете с массивами объектов:

type Todo = {
  id: number;
  title: string;
  completed: boolean;
};

type TodoList = Todo[];

const myTodos: TodoList = [
  { id: 1, title: "Изучить TypeScript", completed: false },
  { id: 2, title: "Написать проект", completed: false },
  { id: 3, title: "Пройти собеседование", completed: false }
];

function getCompletedTodos(todos: TodoList): TodoList {
  return todos.filter(todo => todo.completed);
}

function addTodo(todos: TodoList, title: string): TodoList {
  const newId = Math.max(...todos.map(t => t.id)) + 1;
  return [...todos, { id: newId, title, completed: false }];
}
Совет: Когда type alias для массива содержит только базовый тип (например, string[]), его стоит использовать. Но если массив состоит из сложного объекта, лучше использовать inline-тип для лучшей читаемости.

6. Type alias для функций

TypeScript позволяет создавать type alias для функций. Это полезно, когда у вас есть несколько функций с одинаковой сигнатурой (одинаковыми параметрами и возвращаемым типом).

Синтаксис для type alias функции:

type ИмяФункции = (параметр1: Тип1, параметр2: Тип2) => ВозвращаемыйТип;

Практические примеры:

// Функция, которая принимает два числа и возвращает число
type MathOperation = (a: number, b: number) => number;

// Функция, которая принимает строку и возвращает boolean
type Validator = (input: string) => boolean;

// Функция без параметров, возвращает строку
type Greeting = () => string;

// Функция, которая ничего не возвращает (void)
type Logger = (message: string) => void;

А теперь давайте посмотрим, как использовать эти type alias в реальном коде:

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

// Все эти функции соответствуют типу MathOperation
const add: MathOperation = (a, b) => a + b;
const multiply: MathOperation = (a, b) => a * b;
const subtract: MathOperation = (a, b) => a - b;
const divide: MathOperation = (a, b) => a / b;

// Функция, принимающая MathOperation как параметр
function calculate(operation: MathOperation, x: number, y: number): number {
  return operation(x, y);
}

console.log(calculate(add, 5, 3));       // 8
console.log(calculate(multiply, 5, 3));  // 15
console.log(calculate(divide, 10, 2));   // 5

Обратите внимание, как удобно: функция calculate принимает любую функцию, соответствующую типу MathOperation, и работает с ней. Это открывает возможности для написания гибкого и переиспользуемого кода.

Ещё один полезный пример — type alias для callback-функций:

type Callback = (error: Error | null, data?: any) => void;
type AsyncOperation = (url: string, callback: Callback) => void;

function fetchData(url: string, callback: Callback): void {
  // имитация запроса
  callback(null, { status: "ok" });
}

// Использование
fetchData("https://api.example.com/users", (error, data) => {
  if (error) {
    console.error(error.message);
    return;
  }
  console.log("Данные получены:", data);
});
Запомните: Type alias для функций удобен, когда вам нужно использовать одну и ту же сигнатуру функции в нескольких местах. Если функция используется только один раз, лучше определить её тип прямо в месте использования.

7. Union типы в type

Union-типы — это одна из самых мощных возможностей TypeScript. Union позволяет переменной принимать значение одного из нескольких типов. Вы создаёте union, используя символ вертикальной черты | (pipe) между типами.

Представьте, что у вас есть переменная, которая может быть либо строкой, либо числом:

type StringOrNumber = string | number;

let value1: StringOrNumber = "привет";   // работает
let value2: StringOrNumber = 42;          // работает
let value3: StringOrNumber = true;        // ОШИБКА! boolean не входит в union

В этом примере StringOrNumber — это union-тип, который принимает либо string, либо number. Попытка присвоить boolean вызовет ошибку компиляции.

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

type ID = string | number;

function findUser(id: ID): string {
  if (typeof id === "string") {
    return `Поиск по строковому ID: ${id}`;
  }
  return `Поиск по числовому ID: ${id.toFixed(0)}`;
}

console.log(findUser("user_123"));  // Поиск по строковому ID: user_123
console.log(findUser(42));            // Поиск по числовому ID: 42

Внутри функции TypeScript «сужает» тип: после проверки typeof id === "string" он знает, что в первой ветке id — это строка, а во второй — число.

Вот ещё несколько практических примеров union-типов:

// Переменная может быть строкой, числом или null
type SearchResult = string | number | null;

// Функция может вернуть строку или ошибку
type ApiResponse = string | Error;

// Значение может быть одним из нескольких литералов
type HttpStatus = 200 | 301 | 404 | 500;

// Комбинация примитивов и объектов
type FlexibleValue = string | number | { value: string };

let result: FlexibleValue;
result = "текст";
result = 123;
result = { value: "объект" };

Одно из самых распространённых применений union-типов — обработка результатов, которые могут быть либо успешными, либо ошибочными:

type SuccessResult = {
  success: true;
  data: string;
};

type ErrorResult = {
  success: false;
  error: string;
};

type Result = SuccessResult | ErrorResult;

function processData(input: string): Result {
  if (input.length === 0) {
    return { success: false, error: "Пустой ввод" };
  }
  return { success: true, data: input.toUpperCase() };
}

const result = processData("привет");
if (result.success) {
  console.log(result.data);      // ПРИВЕТ
} else {
  console.error(result.error);  // не выполнится
}
Важно: Когда вы работаете с union-типом, TypeScript требует, чтобы вы проверили, какой именно тип вы имеете, прежде чем использовать специфические свойства. Например, вы не можете обратиться к result.data, не проверив сначала result.success === true. Это называется сужение типа (type narrowing).

8. Union с числовыми литералами

Одна из самых интересных возможностей union-типов — использование литеральных значений вместо общих типов. Вы можете создать тип, который принимает только определённые значения.

Представьте, что у вас есть переменная, которая может быть только одним из нескольких предопределённых значений:

// Статус заказа может быть только одним из этих значений
type OrderStatus = "pending" | "processing" | "shipped" | "delivered" | "cancelled";

let status1: OrderStatus = "pending";      // работает
let status2: OrderStatus = "shipped";       // работает
let status3: OrderStatus = "wrong_status";  // ОШИБКА!

Это гораздо безопаснее, чем просто string! Если вы попытаетесь присвоить недопустимое значение, TypeScript сразу покажет ошибку.

Примеры использования строковых литералов:

// Уровень пользователя
type UserLevel = "beginner" | "intermediate" | "advanced" | "expert";

// День недели
type DayOfWeek = "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday";

// Цвет интерфейса
type Theme = "light" | "dark" | "system";

// Формат файла
type FileFormat = "json" | "xml" | "csv" | "txt";

Числовые литералы работают аналогично:

// HTTP статус-коды
type HttpStatusCode = 200 | 201 | 301 | 400 | 401 | 403 | 404 | 500;

// Уровень логирования
type LogLevel = 1 | 2 | 3 | 4 | 5;

// Рейтинг
type StarRating = 1 | 2 | 3 | 4 | 5;

function rateProduct(rating: StarRating): string {
  switch (rating) {
    case 5: return "Отлично!";
    case 4: return "Хорошо";
    case 3: return "Нормально";
    case 2: return "Плохо";
    case 1: return "Ужасно";
  }
}

rateProduct(5);   // Отлично!
rateProduct(3);   // Нормально
rateProduct(6);   // ОШИБКА! 6 не входит в тип
Преимущество литеральных union-типов: Они работают как « enumerated types» (перечисления) из других языков, но при этом более гибкие. Вы можете комбинировать их с другими типами: type FlexibleRating = StarRating | "none" — добавляет значение "none" к существующему типу.

9. Комбинирование union и объектных типов

Одна из самых мощных возможностей TypeScript — комбинирование union-типов с объектными типами. Это позволяет создавать гибкие структуры данных, которые могут принимать разные формы в зависимости от контекста.

Рассмотрим реальный пример: обработку событий в интерфейсе:

type MouseEvent = {
  type: "mouse";
  x: number;
  y: number;
  button: number;
};

type KeyboardEvent = {
  type: "keyboard";
  key: string;
  code: number;
  altKey: boolean;
};

type TouchEvent = {
  type: "touch";
  x: number;
  y: number;
  touches: number;
};

type AppEvent = MouseEvent | KeyboardEvent | TouchEvent;

function handleEvent(event: AppEvent): void {
  switch (event.type) {
    case "mouse":
      console.log(`Клик в точке (${event.x}, ${event.y}) кнопкой ${event.button}`);
      break;
    case "keyboard":
      console.log(`Нажата клавиша: ${event.key}`);
      break;
    case "touch":
      console.log(`Касание в точке (${event.x}, ${event.y}) ${event.touches} пальцев`);
      break;
  }
}

Здесь каждый объектный тип содержит поле type с литеральным значением. Когда вы проверяете event.type === "mouse", TypeScript автоматически «сужает» тип до MouseEvent, и вы получаете доступ к свойствам x, y и button.

Другой полезный пример — обработка результатов API-запросов:

type LoadingState = {
  status: "loading";
};

type SuccessState<T> = {
  status: "success";
  data: T;
};

type ErrorState = {
  status: "error";
  message: string;
};

type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;

// Использование с типом User
type User = { name: string; email: string };
type UserState = AsyncState<User>;

function renderUserState(state: UserState): string {
  switch (state.status) {
    case "loading":
      return "Загрузка...";
    case "success":
      return `Пользователь: ${state.data.name}`;
    case "error":
      return `Ошибка: ${state.message}`;
  }
}
Паттерн «discriminated union»: Это когда каждый вариант union-типа содержит общее поле (в нашем случае status) с уникальным литеральным значением. TypeScript использует это поле для автоматического сужения типа — вы можете использовать switch или if для определения конкретного типа.

10. Type alias vs interface

В TypeScript есть два способа определять пользовательские типы: type и interface. Многие новички задаются вопросом: в чём разница? Давайте разберёмся.

Синтаксис:

// Type alias
type UserType = {
  name: string;
  email: string;
};

// Interface
interface UserInterface {
  name: string;
  email: string;
}

Для объектов оба варианта работают одинаково. Но есть несколько ключевых различий:

1. Interface можно расширять (extends):

// Interface можно расширять
interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

// Type alias можно использовать с intersection (&)
type AnimalType = {
  name: string;
};

type DogType = AnimalType & { breed: string };

2. Type alias для union-типов:

// Type alias для union
type StringOrNumber = string | number;

// Type alias для примитивов
type ID = string | number;

// Interface НЕ поддерживает union
// interface StringOrNumber = string | number;  // ОШИБКА!

3. Declaration merging (только interface):

// Interface можно объявлять несколько раз — они сливаются
interface Window {
  title: string;
}

interface Window {
  alert(s: string): void;
}

// Window теперь имеет и title, и alert

Вот сводная таблица различий:

Возможность type interface
Объектные типы Да Да
Union-типы Да Нет
Primitives Да Нет
extends (наследование) Нет Да
Declaration merging Нет Да
Compute types Да Нет
Рекомендация: Если вы определяете объектный тип, используйте interface — он лучше подходит для расширения и слияния. Для union-типов, примитивов и более сложных вычисляемых типов используйте type. На практике разница невелика, и многие команды используют оба варианта вперемешку.

11. Использование type alias в функциях

Type alias становится особенно полезным, когда вы используете сложные типы в параметрах и возвращаемых значениях функций. Давайте рассмотрим несколько практических примеров.

Пример 1: Функция для форматирования данных пользователя:

type User = {
  firstName: string;
  lastName: string;
  age: number;
  email: string;
};

type FormattedUser = {
  fullName: string;
  contactInfo: string;
  isAdult: boolean;
};

function formatUser(user: User): FormattedUser {
  return {
    fullName: `${user.firstName} ${user.lastName}`,
    contactInfo: `${user.email} (возраст: ${user.age})`,
    isAdult: user.age >= 18
  };
}

const user: User = {
  firstName: "Алексей",
  lastName: "Иванов",
  age: 25,
  email: "alex@example.com"
};

const formatted = formatUser(user);
console.log(formatted.fullName);    // "Алексей Иванов"
console.log(formatted.isAdult);      // true

Пример 2: Функция с опциональными параметрами:

type PaginationOptions = {
  page?: number;
  limit?: number;
  sortBy?: string;
  order?: "asc" | "desc";
};

type PageResult<T> = {
  items: T[];
  total: number;
  page: number;
  hasMore: boolean;
};

function getItems<T>(
  items: T[],
  options: PaginationOptions = {}
): PageResult<T> {
  const { page = 1, limit = 10 } = options;
  const start = (page - 1) * limit;
  const end = start + limit;

  return {
    items: items.slice(start, end),
    total: items.length,
    page,
    hasMore: end < items.length
  };
}

// Использование
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
const page1 = getItems(numbers, { page: 1, limit: 3 });
console.log(page1.items);     // [1, 2, 3]
console.log(page1.hasMore);    // true

Пример 3: Функция с union-типами в параметрах:

type StringOrNumber = string | number;
type Callback = (value: string) => void;

function processValue(input: StringOrNumber, callback: Callback): void {
  let result: string;

  if (typeof input === "string") {
    result = input.toUpperCase();
  } else {
    result = input.toString();
  }

  callback(result);
}

processValue("привет", (value) => console.log(value));  // ПРИВЕТ
processValue(42, (value) => console.log(value));     // 42
Совет: Используйте type alias для параметров функций, когда сигнатура функции становится сложной. Это улучшает читаемость и делает код более поддерживаемым. Например, вместо function save(user: { name: string; email: string; age: number }) лучше написать function save(user: User).

12. Вложенные type aliases

Type alias может ссылаться на другие type alias, создавая сложные структуры данных. Это позволяет разбивать сложные типы на более мелкие и управляемые части.

Пример: определение структуры для интернет-магазина:

// Базовые типы
type Money = number;
type Currency = "RUB" | "USD" | "EUR";
type Email = string;
type PhoneNumber = string;

// Промежуточные типы
type Address = {
  street: string;
  city: string;
  country: string;
  zipCode: string;
};

type ContactInfo = {
  email: Email;
  phone: PhoneNumber;
  address: Address;
};

// Составные типы
type Product = {
  id: number;
  name: string;
  price: Money;
  currency: Currency;
  inStock: boolean;
};

type OrderItem = {
  product: Product;
  quantity: number;
  subtotal: Money;
};

type Order = {
  id: string;
  items: OrderItem[];
  total: Money;
  currency: Currency;
  shippingAddress: Address;
  status: "pending" | "paid" | "shipped" | "delivered";
};

type Customer = {
  id: number;
  name: string;
  contact: ContactInfo;
  orders: Order[];
};

Обратите внимание, как каждый тип собирается из предыдущих. Это создаёт «пирамиду» типов, где каждый уровень добавляет новую абстракцию. Такой подход делает код чище и проще для понимания.

Ещё один пример — типизация конфигурации приложения:

type DatabaseConfig = {
  host: string;
  port: number;
  username: string;
  password: string;
  database: string;
};

type ServerConfig = {
  port: number;
  host: string;
  cors: boolean;
};

type LogConfig = {
  level: "debug" | "info" | "warn" | "error";
  file?: string;
};

type AppConfig = {
  env: "development" | "production" | "test";
  database: DatabaseConfig;
  server: ServerConfig;
  logging: LogConfig;
};

const config: AppConfig = {
  env: "development",
  database: {
    host: "localhost",
    port: 5432,
    username: "admin",
    password: "secret",
    database: "myapp"
  },
  server: {
    port: 3000,
    host: "localhost",
    cors: true
  },
  logging: {
    level: "debug"
  }
};

Вложенные type alias позволяют создавать сложные структуры, оставаясь при этом читабельными. Каждый тип решает одну задачу, а верхнеуровневый тип собирает их вместе.

Принцип: Разделяйте сложные типы на более мелкие,осмысленных части. Этоследует принципа единственной ответственности (Single Responsibility Principle), но применённого к типам. Каждый type alias должен описывать одну концепцию.

13. Ошибки новичков

При работе с type alias новички часто допускают ошибки. Давайте рассмотрим самые распространённые из них, чтобы вы могли их избежать.

Ошибка 1: Забытая точка с запятой в объектном типе

// НЕПРАВИЛЬНО — пропущена точка с запятой
type User = {
  name: string
  email: string  // ← ошибка!
}

// ПРАВИЛЬНО
type User = {
  name: string;  // ← точка с запятой
  email: string;  // ← точка с запятой
};

Ошибка 2: Использование маленькой буквы для имени типа

// НЕПРАВИЛЬНО — имя начинается с маленькой буквы
type user = {  // ← ошибка!
  name: string;
};

// ПРАВИЛЬНО — PascalCase
type User = {
  name: string;
};

Ошибка 3: Путаница между type и interface

// type для union
type StringOrNumber = string | number;  // OK

// interface для union — ОШИБКА!
// interface StringOrNumber = string | number;  // НЕ РАБОТАЕТ

// interface для расширения
interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

Ошибка 4: Попытка использовать тип после присваивания (runtime)

type User = {
  name: string;
  age: number;
};

// Попытка проверить тип в runtime — ОШИБКА
const user = { name: "Алексей", age: 25 };

// Неправильно — type существует только в compile-time
// if (user instanceof User) { ... }  // НЕ РАБОТАЕТ

// Правильно — проверяем наличие свойств
if ("name" in user && "age" in user) {
  console.log("Это User!");
}

Ошибка 5: Забытый return в функции, возвращающей type alias

type Point = { x: number; y: number };

// НЕПРАВИЛЬНО — забыли return
function createPoint(x: number, y: number): Point {
  { x, y }  // ← ошибка! Нет return
}

// ПРАВИЛЬНО
function createPoint(x: number, y: number): Point {
  return { x, y };
}

Ошибка 6: Лишний type alias для простых случаев

// Плохо — слишком простой type alias
type StringType = string;
type NumberType = number;
// Это бессмысленно — просто используйте string и number

// Хорошо — осмысленные имена
type UserName = string;  // имеет смысл — это имя пользователя
type UserId = string;    // имеет смысл — это идентификатор
Золотое правило: Создавайте type alias только тогда, когда это добавляет ценность — делает код чище, читабельнее или безопаснее. Не создавайте type alias просто потому, что можете. Если простой string или number достаточно понятен в контексте — используйте его напрямую.

Практический пример: Полная система типов для приложения

Давайте соберём всё вместе и создадим полноценную систему типов для небольшого приложения — список задач (todo list):

// ==========================================
// Система типов для приложения "Список задач"
// ==========================================

// Базовые примитивные type alias
type TodoId = number;
type UserId = number;
type Title = string;
type Description = string;
type Priority = "low" | "medium" | "high";
type Status = "todo" | "in_progress" | "done";

// Объектные type alias
type Todo = {
  id: TodoId;
  title: Title;
  description?: Description;
  priority: Priority;
  status: Status;
  createdAt: Date;
  completedAt?: Date;
};

type User = {
  id: UserId;
  name: string;
  email: string;
};

// Type alias для массивов
type TodoList = Todo[];
type UserList = User[];

// Type alias для функций
type FilterFn = (todo: Todo) => boolean;
type SortFn = (a: Todo, b: Todo) => number;
type Callback = (todo: Todo) => void;

// Union type для фильтрации
type FilterStatus = Status | "all";
type FilterPriority = Priority | "all";

// Сложный union для результатов
type OperationResult = {
  success: true;
  todo: Todo;
} | {
  success: false;
  error: string;
};

// ==========================================
// Функции с использованием type alias
// ==========================================

function createTodo(
  title: Title,
  priority: Priority,
  description?: Description
): Todo {
  return {
    id: Date.now(),
    title,
    description,
    priority,
    status: "todo",
    createdAt: new Date()
  };
}

function filterTodos(todos: TodoList, filter: FilterFn): TodoList {
  return todos.filter(filter);
}

function getTodosByStatus(
  todos: TodoList,
  status: FilterStatus
): TodoList {
  if (status === "all") {
    return todos;
  }
  return todos.filter(todo => todo.status === status);
}

function getTodosByPriority(
  todos: TodoList,
  priority: FilterPriority
): TodoList {
  if (priority === "all") {
    return todos;
  }
  return todos.filter(todo => todo.priority === priority);
}

function completeTodo(todo: Todo): Todo {
  return {
    ...todo,
    status: "done" as Status,
    completedAt: new Date()
  };
}

// ==========================================
// Использование
// ==========================================

const todos: TodoList = [
  createTodo("Изучить TypeScript", "high"),
  createTodo("Написать проект", "medium"),
  createTodo("Выпить кофе", "low"),
];

const highPriority = getTodosByPriority(todos, "high");
console.log(`Высокий приоритет: ${highPriority.length} задач`);

const allTodos = getTodosByStatus(todos, "all");
console.log(`Всего задач: ${allTodos.length}`);

Этот пример демонстрирует, как type alias могут организовать типизацию целого приложения. Каждый тип имеет осмысленное имя, функции используют эти типы, а код остаётся чистым и читабельным.

Итоги урока

Поздравляем! Вы изучили один из фундаментальных инструментов TypeScript. Давайте подведём итоги:

Что мы изучили:

  • Что такое type alias — собственные имена для типов, которые делают код чище и понятнее
  • Синтаксис — type Name = Type; с именами в PascalCase
  • Примитивные type alias — дают осмысленные имена примитивным типам для улучшения читаемости
  • Объектные type alias — определяют структуру объектов и избавляются от дублирования
  • Массивные type alias — Type[] и Array<Type>
  • Функциональные type alias — определяют сигнатуру функций
  • Union-типы — переменная может быть одним из нескольких типов (через |)
  • Литеральные union-типы — ограничение значений конкретными литералами
  • Комбинирование — union + объектные типы для гибких структур данных
  • type vs interface — когда что использовать
  • Вложенные type alias — создание сложных структур из простых частей
  • Типичные ошибки — как их избежать
Помните: Type alias — это не просто «синтаксический сахар». Это инструмент для создания чистого, безопасного и поддерживаемого кода. Используйте его с умом, и ваш код станет значительно лучше!

Советы для запоминания:

  • Начните с простого — создавайте type alias для примитивов и смотрите, как код становится чище
  • Используйте union-типы для переменных, которые могут принимать разные значения
  • Не злоупотребляйте — создавайте type alias только тогда, когда это добавляет ценность
  • Практикуйтесь — создавайте type alias для реальных задач, с которыми вы сталкиваетесь
Домашнее задание: Попробуйте создать систему типов для приложения, которое вы хотите построить. Определите основные объекты (пользователи, товары, заказы) как type alias, создайте union-типы для статусов и фильтров, и напишите несколько функций с использованием ваших типов.

Это был последний урок Модуля 3!

Вы успешно освоили основы типов в TypeScript

Готовы? Переходите к Модулю 4: Функции

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

8 вопросов

Type alias для пользователя

Premium