Menu

Interface vs Type di TypeScript: Perbedaan dan Kapan Dipakai

interface dan type sama-sama bisa menjelaskan bentuk object, dan sebagian besar waktu keduanya sama-sama bisa dipakai. Pelajari perbedaan yang sebenarnya: declaration merging, union dan mapped type, extends vs intersection, index signature implisit, pelaporan error dan performa compiler, ditambah aturan yang jelas untuk memilih.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Untuk menjelaskan bentuk sebuah object, interface dan type melakukan tugas yang sama, dan nilai dari yang satu bisa di-assign ke yang lain kalau bentuknya cocok. Perbedaannya ada di pinggiran: type bisa memberi nama pada hal-hal yang tidak bisa dilakukan interface (union, tuple, tipe hasil komputasi), dan interface bisa melakukan beberapa hal yang tidak bisa dilakukan type alias (merging, extends yang diperiksa).

TypeScript membandingkan tipe object berdasarkan struktur, jadi nama deklarasinya tidak berpengaruh pada assignability. Berikut daftar tempat pilihan itu benar-benar berpengaruh.

Tabel Perbandingan

Fiturinterfacetype
Bentuk objectyaya
Opsional, readonly, method, index signatureyaya
Genericyaya
Union (A | B)tidakya
Tuple, tipe primitif, tipe fungsi yang berdiri sendiritidak (tipe fungsi hanya sebagai call signature)ya
Mapped type dan conditional typetidakya
Perluasanextends, konflik menjadi error&, konflik menjadi never
Declaration mergingyatidak (duplicate identifier)
Bisa di-assign ke Record<string, T>tidakya, kalau propertinya cocok
implements di classyaya, kalau berupa tipe object
Definisi rekursifyaya

Yang Hanya Bisa Dilakukan type

Apa pun yang bukan satu bentuk object membutuhkan type alias:

Tidak satu pun dari ini bisa ditulis dengan interface (dua yang terakhir dibahas di mapped type dan conditional type). Inilah alasan praktis kenapa setiap codebase pada akhirnya memakai type di suatu tempat, apa pun yang dipakainya untuk bentuk object.

Yang Hanya Bisa Dilakukan interface: Declaration Merging

Dua deklarasi interface dengan nama yang sama di scope yang sama akan digabung menjadi satu. Dua deklarasi type dengan nama yang sama menghasilkan error TS2300, Duplicate identifier.

interface Settings {
  theme: string;
}
interface Settings {
  fontSize: number;
}
const s: Settings = { theme: "dark", fontSize: 14 }; // needs both

type Options = { a: number };
type Options = { b: number }; // error TS2300: Duplicate identifier 'Options'.

Merging adalah cara tipe library diperluas dari luar: menambahkan properti ke Window global, ke Request milik Express, atau ke tipe theme sebuah library. Kalau kamu memublikasikan tipe yang mungkin perlu diperluas oleh penggunanya, pakai interface. Di kode aplikasimu sendiri, merging yang tidak disengaja (dua file script yang mendeklarasikan nama interface global yang sama) terjadi diam-diam, kecuali keduanya mendeklarasikan properti yang sama dengan tipe berbeda. Itulah salah satu alasan sebagian tim lebih memilih type.

extends vs Intersection

Interface diperluas dengan extends, type alias dengan &. Hasil keduanya kebanyakan sama, tetapi cara menangani properti yang bertentangan berbeda. extends melaporkan konflik itu di deklarasinya:

index.ts(7,11): error TS2430: Interface 'Broken' incorrectly extends interface 'Base'.
  Types of property 'id' are incompatible.
    Type 'number' is not assignable to type 'string'.

Intersection menerima konflik yang sama secara diam-diam dan mengubah propertinya menjadi never (sebuah string yang sekaligus number). Error-nya baru muncul belakangan, ketika kamu mencoba membuat sebuah nilai:

Error-nya menunjuk ke object, jauh dari deklarasi yang menyebabkannya. Untuk membangun tipe object dari tipe object lain, extends memberi pesan yang lebih baik.

Index Signature: Perbedaan yang Mudah Terlewat

Type alias untuk tipe object mendapat index signature implisit, jadi bisa dikirim ke tempat yang mengharapkan Record<string, unknown>. Interface tidak. Ini memang disengaja: penjelasan dari tim TypeScript (issue #15300 di repository TypeScript) adalah bahwa interface bisa diperluas oleh deklarasi berikutnya, jadi menyimpulkan index signature untuknya kurang aman, dan mengubah aturan ini sekarang akan merusak terlalu banyak kode.

Pemanggilan yang ditandai @ts-expect-error tetap berjalan dan mencetak field milik Alan: aturan ini hanya berlaku saat kompilasi. Inilah penyebab umum error yang membingungkan ketika nilai bertipe interface dikirim ke helper logging, serialisasi, atau query yang bertipe Record<string, ...>. Ubah deklarasi yang satu itu menjadi type, lakukan spread pada nilainya, atau beri tipe parameter helper itu dengan interface atau generic.

Performa dan Pesan Error

Halaman Performance di wiki TypeScript (bagian "Preferring Interfaces Over Intersections") menyarankan interface Foo extends Bar, Baz { ... } dibandingkan type Foo = Bar & Baz & { ... } saat menyusun tipe object. Alasannya: interface adalah satu tipe object datar yang bisa mendeteksi konflik properti, hubungan tipe antar-interface disimpan di cache (intersection secara keseluruhan tidak), dan memeriksa nilai terhadap intersection berarti memeriksa setiap bagiannya sebelum tipe yang sudah diratakan. Perbedaannya terasa di codebase besar dengan banyak tipe hasil komposisi; untuk beberapa bentuk object biasa, kamu tidak akan bisa mengukurnya.

Halaman yang sama juga mencatat bahwa interface ditampilkan dengan lebih baik. Interface ditampilkan dengan namanya saat hover dan di pesan error, sedangkan alias intersection sering dicetak dalam bentuk terurai menjadi bagian-bagiannya, sehingga pesan yang panjang lebih sulit dibaca.

Mana yang Dipakai

Aturan yang bisa diandalkan:

  1. Bentuk object: interface. Ia memberi extends yang diperiksa, error yang lebih jelas pada komposisi besar, dan bisa diperluas oleh pengguna library. Ini sesuai dengan pedoman TypeScript handbook: "pakai interface sampai kamu perlu memakai fitur dari type".
  2. Semua yang lain: type. Union, tuple, tipe fungsi, tipe literal, dan apa pun yang dibangun dengan mapped type, conditional type, atau template literal type.
  3. Pengecualian: pakai type untuk bentuk object yang harus cocok dengan parameter Record<string, ...>, atau kalau kamu memang sengaja ingin mencegah merging.

Memakai type untuk semuanya juga pilihan yang konsisten, dan banyak codebase melakukannya. Mencampur keduanya secara acak adalah satu-satunya pilihan yang perlu dihindari: pembaca jadi bertanya-tanya apakah ada perbedaan yang disengaja.

Pertanyaan yang sering diajukan

Apa perbedaan type dan interface di TypeScript?

Keduanya menjelaskan bentuk object dan bisa saling menggantikan untuk tujuan itu. type juga bisa memberi nama pada union, tuple, tipe primitif, serta mapped type atau conditional type, yang tidak bisa dilakukan interface. interface mendukung declaration merging dan extends, yang memeriksa properti yang bertentangan. Tipe object yang ditulis dengan type juga bisa di-assign ke tipe index signature seperti Record<string, unknown>, sedangkan interface tidak bisa.

Sebaiknya pakai type atau interface?

Pedoman dari TypeScript handbook: pakai interface sampai kamu butuh fitur yang hanya dimiliki type. Dalam praktiknya, itu berarti interface untuk bentuk object, dan type untuk union, tuple, tipe fungsi, dan tipe hasil komputasi. Tim yang memakai type untuk semuanya juga baik-baik saja; yang penting adalah satu aturan yang konsisten.

Apakah interface lebih cepat daripada type di TypeScript?

Untuk menyusun tipe object, ya, dalam beberapa kasus. Panduan performa dari tim TypeScript menyarankan interface ... extends dibandingkan intersection yang besar (A & B & { ... }), karena hubungan antar-interface disimpan di cache dan sebuah interface adalah satu tipe yang datar. Untuk bentuk object sederhana, tidak ada perbedaan yang berarti.

Bisakah class meng-implement type alias?

Bisa, kalau alias-nya adalah tipe object (atau intersection dari tipe-tipe object): class Point implements PointType { ... } bisa dipakai. Class tidak bisa meng-implement union type; itu adalah error TS2422.

Bisakah interface meng-extend type alias?

Bisa, selama alias-nya adalah tipe object: type Base = { id: string }; interface User extends Base { name: string } valid. Interface tidak bisa meng-extend alias berupa union. Sebaliknya, type alias bisa dibangun di atas interface dengan &.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai