← Назад к вопросам

Когда используется type в TypeScript?

2.0 Middle🔥 201 комментариев
#TypeScript#ООП

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Type vs Interface в TypeScript

Это один из самых часто задаваемых вопросов для TypeScript разработчиков. Вроде бы похоже, но есть важные отличия.

Краткий ответ

Эмпирическое правило:

  • interface — для object shapes (структур данных)
  • type — для всего остального (union, tuple, primitive, function)

Основные отличия

1. Синтаксис и базовое использование

Interface:

interface User {
  name: string;
  email: string;
  age: number;
}

// Используется как обычно
const user: User = {
  name: 'John',
  email: 'john@example.com',
  age: 30
};

Type:

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

// Точно такой же синтаксис
const user: User = {
  name: 'John',
  email: 'john@example.com',
  age: 30
};

Результат: одинаково работают!

2. Union types — ВОТ где type нужен

// ❌ interface НЕЛЬЗЯ использовать для union
interface Result = 'success' | 'error' | 'pending';  // ERROR!

// ✅ type используется для union
type Result = 'success' | 'error' | 'pending';

const status: Result = 'success';  // ✅
const status2: Result = 'unknown';  // ❌ Type error

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

// API Response может быть одного из нескольких видов
type ApiResponse = 
  | { status: 'success'; data: User }
  | { status: 'error'; message: string }
  | { status: 'loading' };

// interface это не может выразить
// interface = всегда объект с фиксированной структурой

3. Tuple types — type только

// ❌ interface для tuple неудобен
interface Pair {
  0: string;
  1: number;
}

// ✅ type для tuple естественен
type Pair = [string, number];

const pair: Pair = ['hello', 42];  // ✅

4. Функции — type более удобен

// interface для функций
interface Greeter {
  (name: string): string;
}

const greet: Greeter = (name) => `Hello, ${name}`;

// type для функций (более понятно)
type Greeter = (name: string) => string;

const greet: Greeter = (name) => `Hello, ${name}`;

5. Declaration Merging — interface только

// ✅ interface можно объединить
interface User {
  name: string;
}

interface User {
  email: string;
}

// Автоматически merged
const user: User = {
  name: 'John',
  email: 'john@example.com'
};

// ❌ type так не может
type User = { name: string };
type User = { email: string };  // ERROR: Duplicate type alias

Когда это полезно:

// Расширение библиотеки
declare global {
  interface Window {
    myCustomProperty: string;
  }
}

// Теперь window.myCustomProperty работает везде

6. Intersection types

// interface - расширяет
interface Named {
  name: string;
}

interface Aged {
  age: number;
}

interface Person extends Named, Aged {}  // ✅

// type - пересекает
type Named = { name: string };
type Aged = { age: number };

type Person = Named & Aged;  // ✅

// Результат одинаковый
const person: Person = {
  name: 'John',
  age: 30
};

7. Primitive types — type только

// ❌ interface для primitives
interface Str extends string {}  // Технически можно, но странно

// ✅ type для primitives
type Str = string;
type Num = number;
type Bool = boolean;

const str: Str = 'hello';
const num: Num = 42;

8. Conditional types — type только

// Это advanced feature, но показывает мощь type
type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>;  // true
type B = IsString<123>;      // false

Полное сравнение в таблице

ФичаInterfaceType
Object shapes✅ отлично✅ отлично
Union types❌ нельзя✅ отлично
Tuple types❌ неудобно✅ отлично
Функции✅ нормально✅ лучше
Primitives❌ нельзя✅ отлично
Declaration merge✅ да❌ нет
Extends/Intersection✅ extends✅ &
Conditional types❌ нет✅ да
PerformanceНемного лучшеНемного хуже
SerializationЯсно что это интерфейсМожет быть что угодно

Мой стиль кодирования: Pragmatic Rules

// Правило 1: Объекты для interface
interface User {
  id: string;
  name: string;
  email: string;
}

interface Post {
  id: string;
  title: string;
  author: User;
}

// Правило 2: Всё остальное type
type UserId = string & { readonly __brand: 'UserId' };  // Branded type
type ApiResponse = 
  | { status: 'success'; data: User }
  | { status: 'error'; message: string };

type Handler = (req: Request) => Promise<Response>;

type Nullable<T> = T | null;  // Utility type

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

Когда type:

// 1. Union types для состояния
type State = 'idle' | 'loading' | 'success' | 'error';

// 2. Результат операции
type Result<T> = 
  | { ok: true; data: T }
  | { ok: false; error: Error };

// 3. Event типы
type Event = 
  | { type: 'USER_CREATED'; userId: string }
  | { type: 'USER_DELETED'; userId: string }
  | { type: 'POST_PUBLISHED'; postId: string };

// 4. Utilities
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type Partial<T> = { [P in keyof T]?: T[P] };

// 5. Функции
type Middleware = (req: Request, res: Response, next: () => void) => void;

Когда interface:

// 1. API Response shapes
interface ApiUser {
  id: string;
  name: string;
  email: string;
}

// 2. Config объекты
interface AppConfig {
  database: DatabaseConfig;
  server: ServerConfig;
}

// 3. Классы
interface Logger {
  info(message: string): void;
  error(message: string): void;
}

class ConsoleLogger implements Logger {
  info(message: string) { console.log(message); }
  error(message: string) { console.error(message); }
}

// 4. ORM модели
interface User {
  id: string;
  name: string;
  createdAt: Date;
}

Смешивание interface и type

// Можно комбинировать
interface BaseUser {
  id: string;
  name: string;
}

type AdminUser = BaseUser & { role: 'admin' };
type GuestUser = BaseUser & { role: 'guest' };

type User = AdminUser | GuestUser;

// Или наоборот
type BaseConfig = { host: string; port: number };

interface AppConfig extends BaseConfig {
  database: string;
}

Performance note

// Interface немного быстрее для типчекинга
// Потому что compiler знает это object shape

// Type требует больше вычислений для unions и conditionals

// На практике:
// - Для большых проектов разница < 100ms
// - Readability > performance здесь

Финальный совет

// Просто правило которое я использую:

// 1. Interface для данных (что вернет API, БД)
interface User { ... }
interface Post { ... }
interface Comment { ... }

// 2. Type для всего остального
type UserId = string & { readonly __brand: 'UserId' };
type ApiResponse = { success: true } | { success: false };
type Middleware = (req: Request) => Promise<Response>;
type Optional<T> = T | undefined;

// 3. Это делает код предсказуемым
// - Если вижу interface -> это object shape
// - Если вижу type -> это может быть что угодно

Когда выбирать что

Выбери interface если:

  • Моделируешь объект (User, Post, Config)
  • Нужен declaration merging
  • Работаешь с классом (implements)

Выбери type если:

  • Union types нужны
  • Tuple/Primitive types
  • Utility types
  • Функции
  • Есть сомнения (type более гибкий)

Правило в большинстве проектов:

70% interface для данных
30% type для логики

В конце концов, оба работают. Но выбор один или другого делает код более читаемым и мейнтейнабильным.

Когда используется type в TypeScript? | PrepBro