Tipe primitif menjelaskan satu nilai; data nyata hampir selalu berupa object dengan banyak field yang saling berkaitan. Untuk itu TypeScript menyediakan interface — cara memberi nama pada sebuah bentuk object sehingga compiler bisa memeriksa setiap object yang lewat terhadap bentuk tersebut. Bersamaannya, ada keluarga function yang logikanya sama untuk banyak tipe: mencari elemen array, membungkus nilai dalam container, mengambil panjang data. Menulis salinan function untuk setiap tipe membuat kode membengkak, dan di sinilah generic masuk. Pada chapter ini kita akan memakai interface dengan properti wajib, opsional, dan readonly, lalu menulis generic beserta batasannya.

Interface sebagai Kontrak Object #

Interface mendeklarasikan nama properti beserta tipenya; object yang diklaim sesuai interface harus memenuhi seluruh syarat itu. Pemeriksaannya bersifat struktural: yang dibandingkan adalah bentuk object, bukan dari mana object itu dibuat — selama properti dan tipenya cocok, object diterima. Properti opsional ditandai dengan ?:

ts
interface Profil {
  nama: string;
  asal?: string;
}

function perkenalkan(profil: Profil): string {
  return profil.asal === undefined
    ? `Nama saya ${profil.nama}`
    : `Nama saya ${profil.nama} dari ${profil.asal}`;
}

console.log(perkenalkan({ nama: "Budi" }));
console.log(perkenalkan({ nama: "Siti", asal: "Bandung" }));

Output yang Dihasilkan:

text
Nama saya Budi
Nama saya Siti dari Bandung

Mari kita telaah baris-baris kode di atas:

  1. Baris 1-4 (interface Profil): nama wajib bertipe string; asal? boleh tidak ada, dan ketika ada nilainya harus string. Dari sisi tipe, asal bernilai string | undefined.
  2. Baris 6-10 (function perkenalkan): karena asal mungkin undefined, kode memeriksanya eksplisit sebelum dipakai merangkai kalimat — dua cabang menghasilkan dua format kalimat.
  3. Baris 12-13 (console.log(perkenalkan(...))): object pertama hanya membawa nama (sah, asal opsional); object kedua membawa keduanya.

Interface juga menjaga sisi lain: properti yang tidak dikenal. Object literal yang membawa prop asing ditolak, dan pesan errornya cukup pandai mengenali salah ketik:

ts
interface Barang {
  nama: string;
  harga: number;
}

const pensil: Barang = { nama: "Pensil", hrga: 2500 };
bash
npx --yes [email protected] --noEmit berlebih.ts
echo kode keluar: $?

Output yang Dihasilkan:

text
berlebih.ts(6,42): error TS2561: Object literal may only specify known properties, but 'hrga' does not exist in type 'Barang'. Did you mean to write 'harga'?
kode keluar: 1

Saran Did you mean to write 'harga'? langsung menunjuk perbaikannya. Properti readonly melengkapi cerita: modifier readonly melarang penulisan ulang setelah object terbentuk:

ts
interface Konfigurasi {
  readonly namaAplikasi: string;
  versi: string;
}

const konfig: Konfigurasi = { namaAplikasi: "Toko", versi: "1.0.0" };

konfig.namaAplikasi = "Toko Baru";
console.log(konfig.namaAplikasi);

Output yang Dihasilkan:

text
Toko Baru

Node.js mencetak "Toko Baru" tanpa protes — readonly terhapus bersama anotasi lainnya saat type stripping, jadi larangan ini murni urusan compiler:

bash
npx --yes [email protected] --noEmit konfigurasi.ts
echo kode keluar: $?

Output yang Dihasilkan:

text
konfigurasi.ts(8,8): error TS2540: Cannot assign to 'namaAplikasi' because it is a read-only property.
kode keluar: 1

Untuk bentuk yang saling melengkapi, interface dapat mewarisi interface lain lewat extends, sehingga kontrak kecil bisa disusun bertingkat:

ts Contoh: interface extends
interface Pengguna {
  nama: string;
}

interface Pelanggan extends Pengguna {
  level: "reguler" | "vip";
}

Semua object bertipe Pelanggan otomatis wajib memiliki nama (warisan Pengguna) ditambah level miliknya sendiri.

interface atau type #

TypeScript punya dua cara memberi nama bentuk data: interface dan type alias. Keduanya tumpang tindih besar untuk object, sehingga pilihan sering menjadi selera tim:

Aspekinterfacetype
Bentuk objectCocokCocok
Union, literal, gabungan tipe primitifTidak bisaBisa (type Arah = "atas" | "bawah")
Pewarisanextends, bisa bertingkatInterseksi & antar alias
Penggabungan deklarasi senamaYa (declaration merging)Tidak — deklarasi ulang error
Kecocokan penggunaan umumKontrak object publikBentuk terhitung, alias union, utilitas tipe

Generic untuk Function Serba Guna #

Function identitas berikut menerima nilai apa pun dan mengembalikannya apa adanya. Tanpa generic, kontrak itu mustahil ditulis: anotasi any meloloskan segalanya tanpa memeriksa, sedangkan tipe tetap seperti string membatasi nilainya. Generic memperkenalkan type parameter T yang diikat saat pemanggilan:

ts
function identitas<T>(nilai: T): T {
  return nilai;
}

const angka = identitas(42);
const teks = identitas("Node.js");

console.log(typeof angka, angka);
console.log(typeof teks, teks);

function panjang<T extends { length: number }>(nilai: T): number {
  return nilai.length;
}

console.log(panjang("TypeScript"));
console.log(panjang([1, 2, 3, 4]));

interface Kotak<T> {
  isi: T;
}

const kotakAngka: Kotak<number> = { isi: 99 };
const kotakTeks: Kotak<string> = { isi: "sembilan puluh sembilan" };

console.log(kotakAngka.isi + 1);
console.log(kotakTeks.isi.toUpperCase());

Output yang Dihasilkan:

text
number 42
string Node.js
10
4
100
SEMBILAN PULUH SEMBILAN

Mari kita telaah bagian-bagian utamanya:

  1. Baris 1-3 (function identitas<T>(nilai: T): T): T adalah type parameter — placeholder tipe yang baru diikat ketika function dipanggil. Satu T untuk parameter dan return type berarti “tipe keluaran sama dengan tipe masukan”.
  2. Baris 5-6 (identitas(42) dan identitas("Node.js")): kita tidak menuliskan T secara eksplisit; compiler menyimpulkannya dari argumen — pemanggilan pertama mengikat T = number, kedua T = string. Karena itu typeof angka terbukti number dan teks terbukti string.
  3. Baris 11-13 (function panjang<T extends { length: number }>): klausa extends membatasi T pada bentuk yang memiliki properti length bertipe number — disebut generic constraint. String dan array lolos karena keduanya punya length.
  4. Baris 18-20 (interface Kotak<T>): interface juga punya type parameter; Kotak<number> dan Kotak<string> adalah dua bentuk berbeda dari satu definisi yang sama.

Batasan pada generic inilah yang menjaga panjang tetap jujur. Nilai number tidak memiliki properti length, sehingga compiler menolaknya meskipun runtime tetap membiarkan (hasilnya undefined):

ts
function panjang<T extends { length: number }>(nilai: T): number {
  return nilai.length;
}

console.log(panjang(42));

Output yang Dihasilkan:

text
undefined
bash
npx --yes [email protected] --noEmit panjang-salah.ts
echo kode keluar: $?

Output yang Dihasilkan:

text
panjang-salah.ts(5,21): error TS2345: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
kode keluar: 1

Pesan error menuliskan batasannya secara harfiah: argumen harus bisa dianggap { length: number }. Pola constraint seperti ini membuat API sulit dipakai salah — daftar lengkap kemampuan generic dibahas pada dokumentasi Generics .

Interface Lintas Module #

Pada proyek nyata, interface hidup di module tersendiri lalu diimpor oleh pemakainya. Module bentuk.ts mengekspor interface Pengguna beserta function yang memakainya:

ts
export interface Pengguna {
  nama: string;
  email: string;
}

export function tampilkan(pengguna: Pengguna): string {
  return `${pengguna.nama} <${pengguna.email}>`;
}

Titik masuk utama.ts mengimpor keduanya — function sebagai nilai, interface sebagai tipe:

ts
import { tampilkan } from "./bentuk.ts";
import type { Pengguna } from "./bentuk.ts";

const budi: Pengguna = { nama: "Budi", email: "[email protected]" };

console.log(tampilkan(budi));

Output yang Dihasilkan:

text

Baris 2 memakai import type karena Pengguna hanya ada di dunia tipe — setelah anotasi dihapus, tidak ada nilai bernama Pengguna untuk diekspor maupun diimpor saat runtime. Pendefinisian lengkap bentuk object beserta opsi-opsinya tersedia pada dokumentasi Object Types .

Kita kini bisa mendeskripsikan kontrak data yang kaya — properti wajib, opsional, readonly, pewarisan, hingga generic dengan batasan — dan mengekspornya lintas module. Satu pertanyaan penting belum tersentuh: seberapa ketat compiler sebaiknya memperliksa kode kita? Jawabannya ada pada satu switch tunggal bernama strict, yang menjadi topik chapter Strict Mode.