Type vs Interface

Cover Image for Type vs Interface
Krzysztof
Krzysztof

Używając TypeScripta, nie sposób nie spotkać się z takimi konstrukcjami, jak Type oraz Interface. W wielu przypadkach można stosować je zamiennie. Posiadają natomiast charakterystyczne cechy, które postaramy się Wam dzisiaj przybliżyć.

Czym jest Interface

Czym właściwie jest Interface? Osoby programujące w językach obiektowych, takich jak np. Java doskonale znają interfejsy. Programiści czystego języka JavaScript mogą czuć się nieco zagubieni, mając pierwszy raz z nimi do czynienia.

W dużym uproszczeniu można powiedzieć, że interfejs to taki kontrakt, który musi zostać wypełniony, gdy chcemy go zaimplementować. Posłużmy się przykładem, żeby nieco lepiej to zobrazować.

Wyobraźmy sobie, że naszym zadaniem jest zaprojektowanie systemu przechowującego informacje o samochodach. Każdy z nich będzie nieco inny, ale chcemy zapewnić w systemie pewne właściwości, takie jak marka, model, moc itp. :

interface Car { brand: string, model: string, horsePower: number, hasParkingSensors: boolean } const firstCar: Car = { brand: "BMW", model: "e46", horsePower: 115, hasParkingSensors: false } const secondCar: Car = { brand: "Tesla", model: "X", horsePower: 670, hasParkingSensors: true }

Tworząc interface, gwarantujemy implementację wszystkich wymaganych pól. W przeciwnym przypadku programista zobaczy błąd:

const firstCar: Car = { brand: "BMW", model: "e46", hasParkingSensors: false }

TS2741: Property 'horsePower' is missing in type '{ brand: string; model: string; hasParkingSensors: false; }' but required in type 'Car'.

Przyjrzyjmy się bliżej Type

W analogiczny sposób możemy przebudować powyższy kod. Tym razem, używając Type:

type Car = { brand: string, model: string, horsePower: number, hasParkingSensors: boolean } const firstCar: Car = { brand: "BMW", model: "e46", horsePower: 115, hasParkingSensors: false } const secondCar: Car = { brand: "Tesla", model: "X", horsePower: 670, hasParkingSensors: true }

Poza jednym znakiem „=” wygląda prawie tak samo, więc gdzie te różnice?

Różnice pomiędzy Type a Interface

1. Syntax
Pierwszą różnicą, która rzuca się w oczy, jest składnia. Jak można zauważyć w przykładzie wyżej, różnice w syntaxie nie dotyczą tylko deklaracji, ale również rozszerzania. Spójrzmy, jak można rozszerzyć interface:

interface Vehicle { numberOfSeats: number } interface Car extends Vehicle { brand: string, model: string, horsePower: number, hasParkingSensors: boolean } const firstCar: Car = { brand: "BMW", model: "e46", horsePower: 115, hasParkingSensors: false, numberOfSeats: 5 }

W przypadku Type zamiast słowa kluczowego extends do rozszerzenia musimy wykorzystać znak &

type Vehicle = { numberOfSeats: number } type Car = Vehicle & { brand: string, model: string, horsePower: number, hasParkingSensors: boolean } const firstCar: Car = { brand: "BMW", model: "e46", horsePower: 115, hasParkingSensors: false, numberOfSeats: 5 }

2. Scalanie interfejsów
Kolejną istotną różnicą jest scalanie (mergowanie) interfejsów. Wracając do przykładu z samochodami, gdy utworzymy kilka interfaców o takiej samej nazwie:

interface Car { brand: string, model: string, horsePower: number, hasParkingSensors: boolean } interface Car { heatedSeats: boolean price?: number } interface Car { gearBox: "manual" | "automatic" airConditioning: boolean }

W wyniku kompilacji TypeScripta efekt będzie taki, jakbyśmy stworzyli jeden duży interface:

interface Car { brand: string, model: string, horsePower: number, hasParkingSensors: boolean, heatedSeats: boolean price?: number, gearBox: "manual" | "automatic" airConditioning: boolean }

Jeśli taki sam zabieg zastosujemy przy wykorzystaniu typów, otrzymamy błąd:

type Car = { brand: string, model: string, horsePower: number, hasParkingSensors: boolean } type Car = { heatedSeats: boolean price?: number } type Car = { gearBox: "manual" | "automatic" airConditioning: boolean }

TS2300: Duplicate identifier 'Car'.

3. Aliasy typów prymitywnych

Trzecią różnicą jest fakt, że typy mogą być aliasami dla typów prymitywnych, unii itp.

type StringOrBooleanOrNumber = string | boolean | number const myNumber: StringOrBooleanOrNumber = 123 const myBoolean: StringOrBooleanOrNumber = false const myString: StringOrBooleanOrNumber = "hello"

Przy użyciu Interface nie dałoby się osiągnąć takiego efektu, gdyż określają one kształt obiektów

4. Mapped Types

Pozostając w tematyce samochodowej, wyobraźmy sobie sytuację, w której chcemy utworzyć obiekt przechowujący informację na temat ilości dostępnych na sprzedaż samochodów danych marek. Zamiast wypisywać wszystkie klucze po kolei i typować, jako number możemy ułatwić sobie zadanie za pomocą MappedTypes:

type Brands = 'mercedes' | 'audi' | 'tesla'; type BrandsForSaleCount = { [key in Brands]: number; } const forSale: BrandsForSaleCount = { mercedes: 2, audi: 3, tesla: 4 };

Taki skrócony zapis jest równoważny z tym:

type BrandsForSaleCount = { mercedes: number; audi: number; tesla: number; }

W przypadku 3 kluczy może nie jest to game changer, ale co gdyby było ich 50…? Niestety za pomocą Interface taka operacja skończyłaby się błędem:

interface BrandsForSaleCount { [key in Brands]: number; }

TS7061: A mapped type may not declare properties or methods.

5. Wydajność

Poza powyższymi różnicami istnieje jeszcze jedna istotna zwłaszcza w dużych projektach, gdzie dodatkowe milisekundy są na wagę złota. W wyniku tego, że rozszerzanie typów jest o wiele bardziej skomplikowane od rozszerzania interfaców, ogólna wydajność aplikacji przemawia mocno za interfacami. Po więcej szczegółów w tym temacie odsyłam Cię do oficjalnej dokumentacji Typescripta (link na samym dole wpisu)

Kiedy używać interfejsów

  • W 99% przypadków użycie interfejsu będzie prawidłowym wyborem. Szczególnie gdy w naszym kodzie niezbędne będzie przeprowadzenie scalania interfejsów

Kiedy stosować typy

  • Gdy mamy potrzebę zdefiniowania typu jako unia, mapped type, tuple itp.
  • Jeśli chcemy stworzyć alias dla typów prymitywnych

Podsumowanie

Na pierwszy rzut oka Type i Interface mogą być stosowane zamiennie. Po głębszym przeanalizowaniu ich możliwości wychodzi na jaw, że Interface jest wydajniejszy oraz pokrywa większość naszych potrzeb. W szczególnych przypadkach takich jak unie, czy aliasy należy sięgnąć po Type. W pozostałych przypadkach starajmy się korzystać z Interface.

A jak jest u Was w projektach, czego używacie najczęściej? Jeżeli widzisz jakiś błąd lub coś, co warto dodać, daj nam koniecznie znać.

Przydatne linki