- Cтруктурная типизация
- Псевдонимы типов
- Совместимость
- Отличия
- Что использовать для React Props и State
- Заключение
Псевдонимы types и interfaces часто используются взаимозаменяемо в TypeScript, решение об использовании одного или другого сводится к удобочитаемости/краткости. Они имеют много общего функционального сходства: как и в случае interfaces, types могут расширять другие types (с помощью оператора пересечения &), также могут быть реализованы или расширены классом. Но, они также отличаются по важности: классы не могут быть реализованы, а интерфейсы не могут расширять объединение types.
В большинстве случаев псевдонимы types в TypeScript используются для создания псевдонимов более сложного типа, такого как объединение других типов, для повторного использования имени.
type Pet = IDog | ICat;С другой стороны, interfaces используются для более традиционных объектно-ориентированных целей, в которых мы определяем форму объекта, а затем используем его в качестве контракта для параметров функции или для реализации класса.
type Pet = IDog | ICat;
interface IAnimal {
age: number;
eat(): void;
speak(): string;
}
function feedAnimal(animal: IAnimal) {
animal.eat();
}
class Animal implements IAnimal {
age = 0;
eat() {
console.log("nom..nom..");
}
speak() {
return "roar";
}
}
interface IDog {}
interface ICat {}По-моему, это одно из наиболее фундаментальных различий между типами и интерфейсами.
Cтруктурная типизация
Псевдонимы типов и интерфейсы также очень похожи. Например, у нас есть interface IAnimal и type AnimalTypeAlias, который присваивает форму объекта имени AnimalTypeAlias.
interface IAnimal {
age: number;
eat(): void;
speak(): string;
}
type AnimalTypeAlias = {
age: number;
eat(): void;
speak(): string;
};
let animalInterface: IAnimal;
let animalTypeAlias: AnimalTypeAlias;
animalInterface = animalTypeAlias;Эти два типа IAnimal и AnimalTypeAlias в основном эквивалентны. Если мы присвоим переменной let animalInterface тип interface IAnimal, а let animalTypeAlias тип type AnimalTypeAlias, а затем назначим один другому, то TypeScript не будет “ругаться”.
Так как TypeScript использует структурную типизацию. Пока у этих двух типов одинаковая структура (animalInterface и animalTypeAlias), на самом деле не имеет значения, что это разные типы. Если мы изменим любое свойство - TypeScript начнёт “ругаться”.
Псевдонимы типов
Псевдонимы типов, согласно имени, могут действовать как псевдонимы для более сложных типов, таких как функция или массив.
type Eat = (food: string) => void;
type AnimalList = string[];Мы также можем создать эквивалент этим types, используя interfaces. Эти два interface теперь функционально эквивалентны.
interface IEat {
(food: string): void;
}
interface IAnimalList {
[index: number]: string;
}Хоть интерфейс IAnimalList и пропускает все методы массива, которые есть у AnimalList, он всё равно работает почти как массив, так как он говорит, что все его ключи будут числами, а значения, на которые они указывают, будут строками. Хотя это пример того, как могут быть похожи interfaces и types, это также пример того, в каких ситуациях более лаконично использовать types.
Через types, мы можем указать (выразить) объединение других типов с помощью типов пересечений &. Например, у нас есть тип Dog, который имеет тип IPet, и ICanine:
type Dog = IPet & ICanine;
interface IPet {
pose(): void;
}
interface ICanine {
bark: boolean;
}Если мы создадим новую переменную этого типа и попытаемся получить доступ к свойствам, то увидим, что у нас есть доступ к свойствам обеих типов.
type Dog = IPet & ICanine;
const dog: Dog;
dog.bark; // свойство ICanine
dog.pose(); // свойство IPet
interface IPet {
pose(): void;
}
interface ICanine {
bark: boolean;
}Также, мы можем сделать это с помощью interface. Хотя в этом случае нам нужно будет создать совершенно новый interface для выражения этого слияния.
type Dog = IPet & ICanine;
interface IDog extends IPet, ICanine {
}Новый interface должен унаследовать как IPet, так и ICanine. Если мы создадим новую переменную интерфейса IDog - у нас будет доступ к свойствам обеих типов. Однако, разница здесь лишь лексическая.
Совместимость
Нет разницы между псевдонимами типов и интерфейсами, когда речь идет об их взаимозаменяемости.
type Pet = {
pose(): void;
};
interface ICanine {
bark: boolean;
}
interface IDog extends ICanine, Pet {
}interface может расширять как interface, так и type. Type может быть пересечением как interface, так и другого type. Даже class может реализовывать implements как interface, так и type.
interface IDog extends ICanine, Pet {}
type Dog = ICanine & Pet;
class HouseDog implements ICanine, Pet {}Отличия
Одним из наиболее существенных функциональных отличий между type и interface является то, что, с type у нас может быть объединение нескольких других type - в данном случае это type PetType, который может быть либо IDog, либо ICat, то с interface, данная концепция не представляется возможной. Если мы попытаемся расширить в IPet тип объединения PetType, то увидим, что TypeScript “ругается” - An interface can only extend an object type or intersection of object types with statically known members.
type PetType = IDog | ICat;
interface IPet extends PetType {} // error
interface IDog {}
interface ICat {}interface - это конкретный контракт и мы не можем иметь одно или другое. Он должен быть заблокирован при объявлении. Тоже самое касается class, реализующего один из этих типов объединения PetType. TypeScript будет “ругаться”, ведь class - это образец того, как создавать экземпляры объектов.
type PetType = IDog | ICat;
interface IPet extends PetType {} // error
class Pet implements PetType {} // error
interface IDog {}
interface ICat {}Ещё одно функциональное различие между interface и type заключается в том, что если у нас два interface с одинаковыми именами, при использовании они будут объединены вместе. Мы получим доступ к свойствам a и b.
interface Foo {
a: string;
}
interface Foo {
b: string;
}
let foo: Foo;
foo.a
foo.bЭто то, что types не поддерживают. Если мы сделаем тоже самое с types, то получим ошибку - Duplicate identifier 'Foo'.
`.
type Foo = {
a: string;
}
type Foo = {
b: string;
}
let foo: Foo;Теперь давай рассмотрим пример, импортируя jQuery.
import $ from "jquery";
$.fn.extend({
customFunction: function() {
// ...
}
});
$("test").customFunction();Мы собираемся расширить jquery, добавив к нему новую функцию. Если попытаемся использовать эту функцию, то получим ошибку, что Property 'customFunction' does not exist on type 'JQuery<HTMLElement>'.
Это потому, что его не существует в интерфейсе jQuery по умолчанию, так как мы только что его добавили.
Для того чтобы расширить JQuery локально, нам нужно создать файл _имя_.d.ts (typing.d.ts), который находится в той же папке, и указать, что наш новый метод является частью интерфейса JQuery.
interface JQuery {
customFunction(): JQuery;
}Теперь, если вернемся к файлу, в котором у нас была ошибка, то мы её уже не обнаружим. Это происходит из-за поведения, когда TypeScript объединяет объявление interface с тем же именем. Однако, если бы автор библиотеки не пометил JQuery как interface в своем файле, мы бы не смогли сделать что-то подобное.
Что использовать для React Props и State
По большому счету, можно использовать то, что ты хочешь (type alias / interface), только будь последовательным. Лично я рекомендую использовать псевдонимы типов type aliases:
- меньше писать
type Props = {}; - синтаксис непротиворечив (ты не смешиваешь интерфейсы с псевдонимами типов для возможных пересечений типов);
// ПЛОХО
interface Props extends OwnProps, InjectedProps, StoreProps {}
type OwnProps = {...}
type StoreProps = {...}
// ХОРОШО
type Props = OwnProps & InjectedProps & StoreProps
type OwnProps = {...}
type StoreProps = {...}typeполезны для объединяемых типов (например,type MyType = TypeA | TypeB); тогда как интерфейсы лучше использовать для объявления форм, а затем для их реализации или расширения.
Заключение
interface и type очень похожи; они оба могут ссылаться на type, которые объявляют структуру чего-либо. Они также поддерживают объединение различных других типов, либо с помощью оператора пересечения & для type, либо с помощью ключевых слов extend для interface.
Мы можем использовать их взаимозаменяемо. interface могут расширяться от других interface и type. Type могут быть комбинацией interface и type. Классы могут применять (implements) - type и interface.
Однако, мы не сможем применить type, если он является объединением | одного типа или другого - interface, и class должны иметь форму, заблокированную в момент объявления.
- используй то, что подходит тебе и твоей команде, просто будь последовательным;
- всегда используй интерфейс для определения общедоступного API при создании библиотечных или сторонних определений типа;
- рассмотри возможность использования
typeдля React Component Props и State.