Interface memberi nama pada bentuk sebuah object: properti yang wajib dimilikinya dan tipe masing-masing properti. Setelah dideklarasikan, kamu memakai nama itu sebagai tipe, dan compiler memeriksa setiap object yang kamu kirim, kembalikan, atau assign terhadapnya.
Pemanggilan terakhir adalah error kompilasi TS2741. Interface dihapus saat kode dikompilasi: output JavaScript-nya tidak menyisakan jejak User sama sekali, dan tidak ada yang memeriksa bentuknya saat runtime.
Mendeklarasikan Interface
Sintaksnya adalah kata kunci interface, sebuah nama (berdasarkan konvensi, PascalCase), dan body yang berisi daftar member. Member bisa dipisahkan dengan titik koma, koma, atau cukup baris baru; titik koma adalah gaya yang umum.
interface Product {
sku: string; // required property
price: number;
tags: string[]; // array property
dimensions: { // nested object type
width: number;
height: number;
};
discount?: number; // optional property
readonly createdAt: Date; // cannot be reassigned
label(): string; // method
}
Interface adalah tipe, bukan nilai. Interface tidak bisa dibuat instance-nya dengan new, tidak punya nilai default, dan obj instanceof Product adalah error TS2693 ('Product' only refers to a type, but is being used as a value here). Untuk memeriksa bentuk saat runtime, tulis sebuah type guard.
Structural Typing dan Pemeriksaan Properti Berlebih
TypeScript membandingkan bentuk, bukan nama. Object apa pun yang punya properti yang diwajibkan cocok dengan interface, entah dideklarasikan dengannya atau tidak. Properti tambahan tidak masalah, dengan satu pengecualian: object literal yang ditulis langsung di tempat interface diharapkan akan melalui excess property check, karena key yang tidak dikenal di sana hampir selalu salah ketik.
Error itu (TS2353) yang menangkap { id: 1, name: "a", emial: "x" } untuk sebuah User: compiler bahkan menyarankan Did you mean to write 'email'? (TS2561).
Properti Opsional dan Readonly
Tanda ? setelah nama membuat properti menjadi opsional: object boleh tidak menyertakannya, dan membacanya menghasilkan T | undefined. readonly melarang properti di-assign ulang setelah object dibuat.
Ada dua batasan yang terlihat di sini. Pertama, readonly hanya berlaku saat kompilasi: dua baris yang ditandai @ts-expect-error tetap berjalan saat kamu menekan Jalankan, dan keduanya berhasil, lalu baris-baris terakhir mengubah apiUrl lewat referensi yang bertipe tanpa readonly. Kedua, sifatnya dangkal: readonly hosts: string[] akan mencegah hosts di-assign ulang tetapi tetap mengizinkan hosts.push(...), dan karena itulah array-nya sendiri diberi tipe readonly string[]. Ia mendokumentasikan dan menegakkan maksudmu di kode yang bertipe; ia tidak membekukan apa pun. Utility type Readonly<T> membuat setiap properti dari interface yang sudah ada menjadi readonly sekaligus.
Method dan Properti Fungsi
Method bisa ditulis sebagai method signature, name(params): ReturnType, atau sebagai properti yang berisi fungsi, name: (params) => ReturnType. Pemanggil memakai keduanya dengan cara yang sama.
Perbedaannya halus: di bawah strictFunctionTypes (bagian dari strict), parameter dari properti bertipe fungsi diperiksa dengan ketat, sedangkan method signature diperiksa lebih longgar (secara bivarian), jadi bentuk properti menangkap sedikit lebih banyak kesalahan. Sintaks method lebih pendek dan merupakan gaya yang lebih umum; keduanya sama-sama baik.
Interface juga bisa menjelaskan sesuatu yang bisa dipanggil atau dipakai sebagai constructor, memakai call signature atau construct signature:
interface Formatter {
(value: number): string; // call signature: the object is a function
locale: string; // and it also has a property
}
interface PointConstructor {
new (x: number, y: number): { x: number; y: number }; // construct signature
}
Index Signature
Kalau nama properti tidak diketahui sebelumnya, index signature menjelaskan semuanya sekaligus: [key: string]: T berarti "key string apa pun, masing-masing berisi T".
Baris-baris terakhir menunjukkan jebakannya: membaca key yang tidak ada diberi tipe number, bukan number | undefined. Opsi compiler noUncheckedIndexedAccess menambahkan | undefined pada setiap pembacaan seperti itu.
Properti bernama boleh berdampingan dengan index signature, tetapi harus cocok dengannya. interface Dict { [key: string]: number; name: string } adalah error TS2411, Property 'name' of type 'string' is not assignable to 'string' index type 'number'. Lebarkan tipe indeksnya ([key: string]: number | string) atau pindahkan bagian yang dinamis ke propertinya sendiri. Untuk map key/value sederhana, Record<string, number> menyatakan hal yang sama dalam satu baris.
Meng-extend Interface
extends membangun interface baru dari satu atau beberapa interface yang sudah ada. Interface turunan punya setiap member parent ditambah member-nya sendiri:
interface Animal {
name: string;
}
interface Pet extends Animal {
owner: string;
}
interface Trained {
commands: string[];
}
interface ServiceDog extends Pet, Trained {
certifiedUntil: Date;
}
// ServiceDog requires: name, owner, commands, certifiedUntil
Interface turunan boleh mendeklarasikan ulang properti parent hanya dengan tipe yang kompatibel (lebih sempit), misalnya kind: "dog" saat parent menyatakan kind: string. Aturannya, dan cara meng-extend type alias, ada di halaman extends.
Meng-implement Interface di Class
class X implements Shape meminta compiler memeriksa bahwa class itu punya semua yang diwajibkan interface. Member yang hilang menjadi error di deklarasi class:
index.ts(7,7): error TS2420: Class 'Circle' incorrectly implements interface 'Shape'.
Property 'area' is missing in type 'Circle' but required in type 'Shape'.
Setelah area() ditambahkan, beberapa class dan bahkan object biasa bisa sama-sama dipakai sebagai Shape:
implements hanyalah pemeriksaan. Ia tidak menambahkan member ke class, dan tidak memberi tipe pada parameter method class untukmu: greet(name) {} di dalam class yang meng-implement greet(name: string): string tetap error TS7006, Parameter 'name' implicitly has an 'any' type. Sebuah class bisa meng-implement beberapa interface: class A implements B, C.
Declaration Merging
Mendeklarasikan interface dengan nama yang sama dua kali di scope yang sama akan menggabungkan keduanya menjadi satu. Ini tidak bisa dilakukan type alias (type kedua dengan nama yang sama menghasilkan error duplicate identifier).
interface Settings {
theme: string;
}
interface Settings {
fontSize: number;
}
// Settings now requires both properties
const s: Settings = { theme: "dark", fontSize: 14 };
Di kode aplikasi, ini jarang kamu inginkan, dan merging yang tidak disengaja bisa membingungkan. Kegunaan sebenarnya adalah menambahkan member ke tipe yang bukan milikmu: opsi sebuah library, atau global seperti Window. Dari dalam sebuah module, bungkus deklarasinya dengan declare global:
declare global {
interface Window {
analytics: { track(event: string): void };
}
}
export {};
Setelah itu, window.analytics.track("signup") lolos pemeriksaan tipe di seluruh proyek. Package definisi tipe mengandalkan mekanisme yang sama; lihat declaration file.
Nilai Default untuk Properti Interface
Interface tidak bisa menyimpan nilai default, karena ia menjelaskan tipe dan dihapus saat runtime. size?: "sm" | "md" = "md" adalah error TS1246, An interface property cannot have an initializer. Jadikan propertinya opsional, lalu isi nilai default-nya di tempat object itu dipakai:
Default destructuring adalah pilihan yang lebih aman: nilai default diterapkan setiap kali nilainya undefined, termasuk size: undefined yang ditulis eksplisit. Versi spread menyalin undefined eksplisit itu ke atas nilai default, dan hasilnya tetap bertipe seolah-olah size selalu terisi. Kalau kamu butuh jaminan itu dari tipenya, aktifkan exactOptionalPropertyTypes, yang membuat size: undefined menjadi error kompilasi untuk size?: ... yang opsional. Class dengan field yang diinisialisasi adalah pilihan lain kalau object-nya juga butuh perilaku.
Interface Generic
Interface bisa menerima parameter tipe, sehingga satu deklarasi bisa dipakai untuk banyak tipe payload:
interface ApiResponse<T> {
ok: boolean;
data: T;
error?: string;
}
interface Page<T> {
items: T[];
nextCursor?: string;
}
interface User {
id: number;
name: string;
}
const res: ApiResponse<Page<User>> = {
ok: true,
data: { items: [{ id: 1, name: "Ada" }], nextCursor: "abc" },
};
ApiResponse<Page<User>> dibaca sebagai "sebuah response yang data-nya adalah satu halaman berisi user". Standard library penuh dengan bentuk seperti ini: Array<T>, Promise<T>, Map<K, V> semuanya adalah interface generic.
Interface vs Type Alias
Alias type bisa menjelaskan bentuk object yang sama, dan untuk tipe object biasa keduanya bisa saling menggantikan. Hanya interface yang bisa di-merge; hanya type alias yang bisa memberi nama pada union, tuple, mapped type, atau conditional type. Aturan praktis dari TypeScript handbook adalah memakai interface sampai kamu butuh fitur yang hanya dimiliki type. Halaman interface vs type berisi perbandingan lengkapnya, termasuk perbedaan Record<string, ...> yang mengejutkan kebanyakan orang.
Pertanyaan yang sering diajukan
Apa itu interface di TypeScript?
Interface adalah deskripsi bernama tentang bentuk sebuah object: nama-nama propertinya, tipenya, mana yang opsional atau readonly, dan method-nya. Compiler memeriksa bahwa nilai yang dipakai sebagai interface itu punya bentuk tersebut. Interface hanya ada saat kompilasi; ia tidak menghasilkan kode JavaScript.
Bagaimana cara memberi nilai default di interface TypeScript?
Tidak bisa: interface menjelaskan tipe, bukan nilai, jadi size: "md" = ... bukan sintaks yang valid. Tandai propertinya sebagai opsional (size?: "sm" | "md") dan terapkan nilai default di tempat object itu dipakai, biasanya dengan default destructuring di parameter fungsi: function render({ size = "md" }: Options). Melakukan spread pada object default ({ ...DEFAULTS, ...options }) juga bisa, tetapi undefined yang ditulis eksplisit di options akan menimpa nilai default-nya.
Bagaimana cara mengecek saat runtime apakah sebuah object meng-implement interface?
Tidak ada cara bawaan, karena interface dihapus saat kompilasi: obj instanceof User adalah error TS2693 ('User' only refers to a type, but is being used as a value here). Tulis fungsi type guard yang memeriksa propertinya, function isUser(x: unknown): x is User { ... }, atau validasi dengan library schema.
Bisakah sebuah interface meng-extend beberapa interface sekaligus?
Bisa. Tulis semuanya setelah extends, dipisahkan dengan koma: interface ServiceDog extends Pet, Trained { ... }. Interface baru itu punya setiap member dari masing-masing parent ditambah member-nya sendiri. Kalau dua parent mendeklarasikan properti yang sama dengan tipe yang tidak kompatibel, deklarasinya menjadi error.
Apa perbedaan interface dan class di TypeScript?
Class ada saat runtime: ia punya constructor, implementasi method, dan new membuat object darinya. Interface hanya menjelaskan bentuk untuk compiler dan dihapus dari output JavaScript. Class bisa mendeklarasikan implements SomeInterface agar compiler memeriksa kecocokannya, dan object biasa apa pun dengan bentuk yang benar juga cocok dengan interface tersebut.