Type ve Interface: Nedir ve farkları nelerdir?

Mehmet Mutlu
Sahibinden Technology
5 min readFeb 27, 2024

JavaScript ekosisteminde type safe bir kod yazmak istediğimiz zaman akla ilk gelen seçenek genelde TypeScript olmaktadır. Bir kez deneyimledikten sonra da çoğumuz için bir vazgeçilmez olmaktadır. TypeScript içerisinde de doğal olarak en çok kullanılan anahtar sözcüklere “interface” ve “type” diyebiliriz. Bu nedenle ikisinin detaylarını ve farklarını araştırıp hem kendimi geliştirmek hem de paylaşmak istedim.

Yazımın başında type olarak adlandırdığım yapıya Type Aliases (Tür Takma Adları) demek daha doğru olacaktır. Çünkü number, string, boolean vb. isimlendirmeler aslında dataya şeklini veren birer type’tır. Burada bahsedeceğimiz yapı ise bu type’ların adlandırılması olarak düşünebileceğimiz Type Aliases yapısıdır.

Type Aliases ve Interface’in Kısaca Farkları

  • Interface’ler başka type veya interface’leri “extends” kullanımı ile extend edebilirken type aliases’lar “intersection (&)” kullanmak zorundadır.
  • Interface’ler yeni alanlar eklemek için birleştirilebilirken (merge) type aliases’lar birleştirilemez.
  • Interface’ler primitive, union, mapped veya conditional type’lar ile kullanılamazken type aliases’lar kullanılabilir.

Type ve Type Aliases

Yukarıda da kısaca bahsettiğim şekilde type, TypeScript’te dataların şeklini belirleyen bir anahtar sözcüktür. Type’a örnek olarak her birinin farklı özellikleri olan string, number, undefined, boolean, any vb. verilebilir. Type aliases ise kelime anlamı olarak herhangi bir type veya type’ların takma adı olarak düşünülebilir ve yeni bir type yaratmaz. Type aliases oluşturmak için “type” anahtar sözcüğü kullanılır.

type NewNumberType = number;

type NewType = {
id: number;
text: string;
}

Interface

Interface de aslında type aliases gibi bir object type’ını adlandırmanın farklı bir yoludur ve “interface” anahtar sözcüğünü kullanarak oluşturulur. Ayrıca TypeScript’in ilk sürümünden beri var olup nesne yönelimli programlamadan (OOP) ilham alır. Bununla birlikte object type’ları oluşturmak için kalıtımdan faydalanmanıza olanak tanır.

interface NewInterface {
id: number;
text: string;
}

Type Aliases ve Interface Arasındaki Detaylı Farklar

İlk başlık altında bahsetmiş olduğum önemli farklarını ve detaylarını bu başlık altında açıklayacağım.

Interface ve Type Aliases’ların Genişletilmesi (Extends — Intersection)

Interface’leri genişletmek için “extends” anahtar sözcüğünü kullanabiliriz. Bu şekilde oluşturacağımız yeni interface, var olan bir interface veya type’ın özelliklerine sahip olacaktır. Bu durum da daha önce bahsetmiş olduğum kalıtıma bir örnek olarak düşünülebilir.

interface FirstInterface {
id: number;
}

interface SecondInterface extends FirstInterface {
name: string;
surname: string;
}

const User: SecondInterface = {
id: 1,
name: "Mehmet",
surname: "Mutlu",
}

type FirstType = {
phone: string;
}

interface ThirdInterface extends FirstType {
address: string;
}

const UserInfo: ThirdInterface = {
phone: "555 444 33 22",
address: "Street, City, Country"
}

Örnekten de anlaşılacağı üzere interface’ler type’ları çoğullayabilirken type aliases’ların böyle bir yeteneği bulunmamaktadır fakat kendilerine özgü “intersection” adı verilen bir genişletme yöntemine sahiptirler.

type FirstType = {
name: string;
surname: string;
}

type SecondType = {
id: number;
phone: string;
}

type ThirdType = FirstType & SecondType;

const User: ThirdType = {
id: 1,
name: "Mehmet",
surname: "Mutlu",
phone: "555 444 33 22",
}

Bu kullanımlarda eğer conflict oluştursa interface’ler hata verirken type aliases’lar bunu union type olarak kullanacaktır ve hata vermeyecektir.

interface INumberToStringArray {
getId: (number[]) => string[];
}

interface INumberToString extends INumberToStringArray {
getId: (number) => string;
}
// Interface 'INumberToString' incorrectly
// extends interface 'INumberToStringArray'.

type NumberToStringArray = {
getId: (number[]) => string[];
}

type NumberToString = NumberToStringArray & {
getId: (number) => string;
}

const Identities: NumberToString = {
getId: (id: number | number[]) =>
return typeof id === 'number' ? `ID-${id}`: id.map(item => `ID-${item}`);
};

Birleştirilebilme (Merge Edilebilme)

Interface’ler aynı ad ile tekrar tekrar oluşturulabilir ve oluşturulan bu interface’lerin özellikleri o ad altında birleştirilir. Fakat bu özellik type aliases’larda bulunmamaktadır ve aynı isimler tekrar tanımlanırlarsa “Duplicate identifier” uyarısı alınacaktır. Bu kullanım türü interface’ler için fayda sunabilirken karmaşıklığa da neden olabileceği unutulmamalıdır.

interface NewInterface {
name: string;
}

interface NewInterface {
surname: string;
}

const User: NewInterface = {
name: "Mehmet",
surname: "Mutlu"
}

// Duplicate identifier 'NewType'.
type NewType = {
age: number;
}

// Duplicate identifier 'NewType'.
type NewType = {
address: string;
}

Bu kullanım şekli dilin istenen ve beklenen bir özelliğidir. Fakat hazırlıksız olmak sorunlar doğurabileceği için eslint’in no-redeclare özelliğinden yararlanılabilir.

Farklı Type’lar ile Kullanılabilirlik

Interface’i açıklarken de bahsetmiş olduğum gibi interface sadece object type tanımlamak için kullanılırken type aliases’lar primitive, union, mapped, conditional vb. type’lar için kullanılabilir.

TypeScript’in build-in type’larıdır. Bunlara örnek olarak string, number, undefined, boolean, null vb. gösterilebilir.

type NewPrimitiveNumberType = number;

Birçok farklı type aliases’tan, farklı primitive type’lardan, karmaşık type’lardan veya interface’lerden oluşabilir.

type FirstUnionType = number | string;

type NewType = {
name: string;
surname: string;
}

interface NewInterface {
name: string;
surname: string;
phone: string;
}

type SecondUnionType = NewType | NewInterface;

TypeScript’te tekrarı azaltmak adına union type’ları map ederek yeni type aliases’lar yaratmamıza olanak tanıyan bir özelliktir.

type NewUnionType = "blue" | "yellow" | "orange";

type NewType = {
[T in NewUnionType]: {
name: T;
};
};

// {
// blue: { name: "blue" };
// yellow: { name: "yellow" };
// orange: { name: "orange" };
// }

Keyof anahtar sözcüğü ile var olan bir type aliases veya interface’ten kolay bir şekilde yeni bir type aliases yaratılabilir.

interface NewInterface {
name: string;
age: number;
}

type NewType = {
[T in keyof NewInterface]: NewInterface[T] | null;
};

// {
// name: string | null;
// age: number | null;
// }

Bir koşula göre type aliases’ları deterministik bir şekilde tanımlamıza imkan sağlar. Bu duruma type düzeyinde uygulanan bir ternary operatör kullanımı diyebiliriz.

type NewConditionalType = SomeType extends OtherType ? TrueType : FalseType

Buradaki “extends” anahtar sözcüğünü OtherType’ın SomeType’daki type’lara sahip olup olmadığını kontrol etmek için kullanılır. Eğer sahipse TrueType atanırken sahip olmadığı durumda FalseType atanacaktır.

Sonuç

Bu yazımda interface ve type aliases’ların kullanılabilecek alanlarından ziyade farklılıklarına dikkat çekmeyi amaçladım. Bu amacımı yerine getirirken de birçok okuma yaptım ve bu okumalar sonucunda özellikle developerların “type” kullanımını, sunduğu esnekliklerden dolayı daha çok önerdiğini gözlemlemiş oldum. Fakat TypeScript resmi dokümanlarında ise “interface” kullanımının önceliklendirildiğini gözlemledim. Buradaki tercihin kullanılacak alana göre belirlenip iki kullanım türünün de güçlü yanlarından faydalanılması gerekir. Güçlü yanlarını daha derinlemesine araştırmak ve TypeScript hakkında detaylı bilgi edinmek için typescriptlang.org faydalı ve açıklayıcı bir kaynak olacaktır.

Referanslar

--

--