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 ?:
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:
Nama saya Budi
Nama saya Siti dari BandungMari kita telaah baris-baris kode di atas:
- Baris 1-4 (
interface Profil):namawajib bertipestring;asal?boleh tidak ada, dan ketika ada nilainya harusstring. Dari sisi tipe,asalbernilaistring | undefined. - Baris 6-10 (
function perkenalkan): karenaasalmungkinundefined, kode memeriksanya eksplisit sebelum dipakai merangkai kalimat — dua cabang menghasilkan dua format kalimat. - Baris 12-13 (
console.log(perkenalkan(...))): object pertama hanya membawanama(sah,asalopsional); 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:
interface Barang {
nama: string;
harga: number;
}
const pensil: Barang = { nama: "Pensil", hrga: 2500 };npx --yes [email protected] --noEmit berlebih.ts
echo kode keluar: $?Output yang Dihasilkan:
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: 1Saran Did you mean to write 'harga'? langsung menunjuk perbaikannya. Properti readonly melengkapi cerita: modifier readonly melarang penulisan ulang setelah object terbentuk:
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:
Toko BaruNode.js mencetak "Toko Baru" tanpa protes — readonly terhapus bersama anotasi lainnya saat type stripping, jadi larangan ini murni urusan compiler:
npx --yes [email protected] --noEmit konfigurasi.ts
echo kode keluar: $?Output yang Dihasilkan:
konfigurasi.ts(8,8): error TS2540: Cannot assign to 'namaAplikasi' because it is a read-only property.
kode keluar: 1Untuk bentuk yang saling melengkapi, interface dapat mewarisi interface lain lewat extends, sehingga kontrak kecil bisa disusun bertingkat:
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:
| Aspek | interface | type |
|---|---|---|
| Bentuk object | Cocok | Cocok |
| Union, literal, gabungan tipe primitif | Tidak bisa | Bisa (type Arah = "atas" | "bawah") |
| Pewarisan | extends, bisa bertingkat | Interseksi & antar alias |
| Penggabungan deklarasi senama | Ya (declaration merging) | Tidak — deklarasi ulang error |
| Kecocokan penggunaan umum | Kontrak object publik | Bentuk 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:
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:
number 42
string Node.js
10
4
100
SEMBILAN PULUH SEMBILANMari kita telaah bagian-bagian utamanya:
- Baris 1-3 (
function identitas<T>(nilai: T): T):Tadalah type parameter — placeholder tipe yang baru diikat ketika function dipanggil. SatuTuntuk parameter dan return type berarti “tipe keluaran sama dengan tipe masukan”. - Baris 5-6 (
identitas(42)danidentitas("Node.js")): kita tidak menuliskanTsecara eksplisit; compiler menyimpulkannya dari argumen — pemanggilan pertama mengikatT = number, keduaT = string. Karena itutypeof angkaterbuktinumberdanteksterbuktistring. - Baris 11-13 (
function panjang<T extends { length: number }>): klausaextendsmembatasiTpada bentuk yang memiliki propertilengthbertipenumber— disebut generic constraint. String dan array lolos karena keduanya punyalength. - Baris 18-20 (
interface Kotak<T>): interface juga punya type parameter;Kotak<number>danKotak<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):
function panjang<T extends { length: number }>(nilai: T): number {
return nilai.length;
}
console.log(panjang(42));Output yang Dihasilkan:
undefinednpx --yes [email protected] --noEmit panjang-salah.ts
echo kode keluar: $?Output yang Dihasilkan:
panjang-salah.ts(5,21): error TS2345: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
kode keluar: 1Pesan 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:
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:
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:
Budi <[email protected]>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.