В чём разница между interface и type в TypeScript?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Interface vs Type в TypeScript: Полное сравнение
В TypeScript есть два способа определения типов данных: interface и type. На первый взгляд они кажутся взаимозаменяемыми, но у них есть существенные различия, которые влияют на выбор в конкретных ситуациях.
Основное назначение
Interface — предназначен исключительно для определения формы объектов и контрактов для классов. Это более узкоспециализированный инструмент, ориентированный на объектно-ориентированное программирование.
Type — это более универсальный инструмент, который может определять не только объекты, но и примитивы, объединения, пересечения, кортежи и многое другое.
Различия в синтаксисе и возможностях
1. Определение свойств объекта
// Interface
interface User {
name: string;
age: number;
email: string;
}
// Type
type User = {
name: string;
age: number;
email: string;
};
// Оба работают одинаково для объектов
const user: User = {
name: 'Иван',
age: 30,
email: 'ivan@example.com'
};
2. Наследование (extends)
// Interface использует extends
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
breed: string;
bark(): void;
}
const dog: Dog = {
name: 'Шарик',
age: 5,
breed: 'Овчарка',
bark() { console.log('Гав!'); }
};
// Type использует & (пересечение)
type Animal = {
name: string;
age: number;
};
type Dog = Animal & {
breed: string;
bark(): void;
};
3. Объединения (Union Types)
Это возможно ТОЛЬКО с type, а не с interface:
// Type позволяет объединять типы
type Status = 'pending' | 'success' | 'error';
type ID = string | number;
type Response = User | Error;
const status: Status = 'pending'; // OK
const id: ID = 123 || 'user-123'; // OK
// Interface НЕЛЬЗЯ использовать для объединений
// interface Status = 'pending' | 'success'; // ОШИБКА!
4. Кортежи и примитивы
Type может определять практически что угодно, interface — только структуры объектов:
// Type работает с примитивами и кортежами
type StringOrNumber = string | number;
type Coordinates = [number, number];
type Callback = (data: string) => void;
type Nullable<T> = T | null;
// Interface не может этого делать
// interface StringOrNumber = string | number; // ОШИБКА!
5. Объединение объявлений (Declaration Merging)
Interface позволяет объединять несколько объявлений одного имени (merging). Type этого не поддерживает:
// Interface: можно расширять в разных местах кода
interface Window {
myCustomProperty: string;
}
interface Window {
anotherProperty: number;
}
// Результат: свойства из обоих объявлений объединены
const w: Window = {
myCustomProperty: 'test',
anotherProperty: 42
};
// Type: нельзя переобъявлять
type Config = { timeout: number };
type Config = { retries: number }; // ОШИБКА: Duplicate identifier
6. Пересечение типов (Intersection)
// Type: естественно работает с &
type AdminUser = User & { role: 'admin'; permissions: string[] };
type CombinedType = TypeA & TypeB & TypeC;
// Interface: нужно использовать extends
interface AdminUser extends User {
role: 'admin';
permissions: string[];
}
7. Условные типы (Conditional Types)
Это возможно только с type:
// Type поддерживает условные типы
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<123>; // false
type Flatten<T> = T extends Array<infer U> ? U : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number
// Interface не может быть использован в условных типах
8. Дженерики (Generics)
// Interface с дженериками
interface Container<T> {
value: T;
getValue(): T;
}
const stringContainer: Container<string> = {
value: 'hello',
getValue() { return this.value; }
};
// Type с дженериками
type Container<T> = {
value: T;
getValue(): T;
};
// Оба работают одинаково
Практические примеры
Когда использовать Interface
// 1. Описание структуры объекта в приложении
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}
// 2. Контракт для класса
interface PaymentService {
processPayment(amount: number): Promise<boolean>;
refund(transactionId: string): Promise<void>;
}
class StripePayment implements PaymentService {
async processPayment(amount: number): Promise<boolean> {
// Реализация
return true;
}
async refund(transactionId: string): Promise<void> {
// Реализация
}
}
// 3. Расширение встроенных типов
interface String {
reverse(): string;
}
Когда использовать Type
// 1. Объединения и условные типы
type UserRole = 'admin' | 'user' | 'guest';
type ApiResponse<T> = { success: true; data: T } | { success: false; error: string };
// 2. Функциональные типы
type Handler = (event: Event) => void;
type Predicate<T> = (item: T) => boolean;
// 3. Утилиты для типов
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type Partial<T> = {
[K in keyof T]?: T[K];
};
// 4. Кортежи и примитивные типы
type Point = [number, number, number];
type Status = 'active' | 'inactive' | 'pending';
Сравнительная таблица
| Возможность | Interface | Type |
|---|---|---|
| Описание объектов | ✓ | ✓ |
| Наследование/Расширение | ✓ (extends) | ✓ (&) |
| Объединения (Union) | ✗ | ✓ |
| Примитивные типы | ✗ | ✓ |
| Кортежи | ✗ | ✓ |
| Функциональные типы | ✗ | ✓ |
| Declaration Merging | ✓ | ✗ |
| Условные типы | ✗ | ✓ |
| Mapped Types | ✗ | ✓ |
| Реализация в классах | ✓ | ✗ |
Рекомендации по использованию
Используй Interface когда:
- Определяешь структуру объекта, которую будут реализовывать классы
- Нужна Declaration Merging (расширение существующих типов)
- Хочешь явно показать контракт для классов
Используй Type когда:
- Нужны объединения (Union Types)
- Работаешь с условными или mapped типами
- Определяешь функциональные типы
- Нужна большая гибкость
Практический совет
В современных проектах рекомендуется придерживаться одного стиля для консистентности. Многие команды предпочитают использовать type для всех случаев, так как он более универсален. Однако если код использует классы и наследование, interface может быть более понятным.