Примитивные типы
{
type CustomString = string;
const str: CustomString = ‘’;
// ❌
interface CustomStringByInterface = string;
}
Псевдонимы типов могут определять примитивные типы (символ, булевое значение, строку, число, bigint и т. д.), в то время как интерфейсы не могут.
Обратите внимание на то, что псевдоним типа не создает новых типов (отсюда и его название). Интерфейсы, напротив, всегда создают новые типы.
Типы-объединения
{
type Fruit = ‘apple’ | ‘lemon’;
type Vegetable = ‘potato’ | ‘tomato’;
// ‘apple’ | ‘lemon’ | ‘potato’ | ‘tomato’
type Food = Fruit | Vegetable;
const apple: Food = ‘apple’;
}
Типы-объединения также можно определить только с помощью псевдонимов типов.
Типы-кортежи
{
type Animal = [name: string, age: number];
const cat: Animal = [‘’, 1];
}
Типы-кортежи также можно определить только с помощью псевдонимов типов.
Типы объектов/функций
И интерфейс, и тип могут объявлять типы объектов или типы функций. Однако интерфейс может объявлять один и тот же интерфейс несколько раз, и они будут объединены автоматически, в то время как псевдонимы типов не объединяются и должны быть уникальными.
{
// При использовании интерфейсов одинаковые интерфейсы автоматически объединяются.
interface PrintName {
(name: string): void;
}
interface PrintName {
(name: number): void;
}
// ✅
const printName: PrintName = (name: number | string) => {
console.log(‘name: ‘, name);
};
}
{
// При использовании псевдонимов типов они должны быть уникальными, для пересечения можно использовать только `&`.
type PrintName = ((name: string) => void) & ((name: number) => void);
// ✅
const printName: PrintName = (name: number | string) => {
console.log(‘name: ‘, name);
};
}
Другим ключевым моментом является то, что псевдонимы типов используют пересечение, а интерфейсы — наследование.
{
interface Parent {
printName: (name: number) => void;
}
// ❌ Интерфейс 'Child' некорректно расширяет интерфейс 'Parent'.
interface Child extends Parent {
printName: (name: string) => void;
}
}
{
type Parent = {
printName: (name: number) => void;
};
type Child = Parent & {
// Здесь два printName будут пересекаться.
// Это эквивалентно`(name: number | string) => void`
printName: (name: string) => void;
};
const test: Child = {
printName: (name: number | string) => {
console.log(‘name: ‘, name);
},
};
test.printName(1);
test.printName(‘1’);
}
Как показано в ошибке выше, когда интерфейс наследуется, подтип не может конфликтовать с супертипом, его можно только расширить:
{
interface Parent {
printName: (name: number) => void;
}
interface Child extends Parent {
// ✅
printName: (name: string | number) => void;
}
}
Интерфейс использует extends
для реализации наследования, а псевдоним типа — &
для реализации пересечения.
В некоторых случаях автоматическое объединение и расширение интерфейсов может пригодиться. Если вы создадите стороннюю библиотеку и откроете публичные API, пользователи смогут расширять ее через механизм интерфейсов!
Поэтому следуйте такому совету: если хотите объявить тип объекта, используйте сначала интерфейс, а при необходимости — псевдонимы типов.
Сопоставленные типы объектов
type Vegetable = ‘potato’ | ‘tomato’;
{
type VegetableOption = {
[Property in Vegetable]: boolean;
};
const option: VegetableOption = {
potato: true,
tomato: false,
};
// “potato” | “tomato”
type VegetableAlias = keyof VegetableOption;
}
{
interface VegetableOption {
// ❌ Сопоставленный тип не может объявлять свойства или методы.
[Property in Vegetable]: boolean;
}
}
export {};
Сопоставленные типы объектов могут быть определены только с помощью псевдонимов типов, включая ключевые слова in
и keyof
.
Неизвестные типы
{
const potato = { name: ‘potato’, weight: 1 };
// тип Vegetable = {
// имя: string;
// вес: number;
// }
type Vegetable = typeof potato;
const tomato: Vegetable = {
name: ‘tomato’,
weight: 2,
};
}
При работе с неизвестными типами можно применять typeof
для захвата типа, но в этом случае допустимы только псевдонимы типов, а не интерфейсы.
Подводя итог, можно сказать, что псевдонимы типов охватывают почти все возможности интерфейсов. Однако интерфейсы всегда расширяемы, а псевдонимы типов — нет. Поэтому приходится делать выбор между ними в зависимости от конкретного случая.
Читайте также:
- Замечательные новые фичи TypeScript 3.5
- Индексация строк в Rust и TypeScript в сравнениях
- 7 инструментов для разработки веб-компонентов
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Lee: Differences Between Type Aliases and Interfaces in TypeScript 4.6