Anotasi tipe adalah kosakata dasar TypeScript; sebelum membahas interface dan generic, kita perlu menguasai cara menjelaskan bentuk data yang paling sederhana. Kabar baiknya, sebagian besar tipe yang kita tulis sehari-hari hanya segelintir nama: string, number, boolean, array, dan gabungannya. Pada chapter ini kita akan menulis anotasi pada variable dan function, memakai union untuk nilai yang boleh lebih dari satu tipe, mengenal tipe literal yang mempersempit nilai (bukan hanya tipenya), lalu membedakan any yang mematikan pemeriksaan dari unknown yang justru memaksa kita memeriksa.

Tipe Primitif #

Tiga tipe primitif menutup hampir semua data harian: string untuk teks, number untuk segala jenis angka (TypeScript tidak membedakan bilangan bulat dan desimal), dan boolean untuk dua keadaan. Anotasi ditulis setelah nama pengenal dengan format nama: tipe:

ts
const namaBarang: string = "Pensil";
const harga: number = 2500;
const tersedia: boolean = true;

function deskripsiBarang(nama: string, harga: number, tersedia: boolean): string {
  const status = tersedia ? "tersedia" : "habis";
  return `${nama} - ${status} - Rp${harga}`;
}

console.log(deskripsiBarang(namaBarang, harga, tersedia));
console.log(typeof namaBarang, typeof harga, typeof tersedia);

Output yang Dihasilkan:

text
Pensil - tersedia - Rp2500
string number boolean

Mari kita telaah baris-baris kode di atas:

  1. Baris 1-3 (const namaBarang: string = ... ddst.): tiga variable primitif dianotasi eksplisit. Nilainya tentu saja cocok — anotasi baru berbicara ketika ada nilai yang tidak cocok.
  2. Baris 5 (function deskripsiBarang(...): string): ketiga parameter dianotasi, dan bagian setelah kurung tutup adalah return type — janji bahwa function ini mengembalikan string.
  3. Baris 6-7 (const status = ...): variable lokal status tidak dianotasi; compiler menyimpulkan tipenya sendiri dari nilai hasil operator ternary.
  4. Baris 11 (console.log(typeof ...)): operator typeof JavaScript memastikan bahwa pada runtime, nilai di balik anotasi memang bertipe string, number, dan boolean — anotasi tidak mengubah nilai, hanya menjelaskannya.

Ringkasan tipe yang telah kita pakai beserta dua tetangga yang sering muncul:

TipeContoh nilaiKeterangan
string"Budi"Teks; dibungkus kutip tunggal, kutip ganda, atau backtick
number2500, 0.5Semua angka, bulat maupun desimal
booleantrueHanya true dan false
null dan undefinednullAda secara bawaan; perannya besar pada chapter Strict Mode
void—Untuk function yang tidak mengembalikan nilai; kita jumpai sebentar lagi

Array #

Array dianotasi dengan tipe elemen diikuti []. Sebuah array number ditulis number[], array string ditulis string[]; bentuk Array<number> tersedia sebagai padanan yang jarang dipakai:

ts
const stok: number[] = [10, 25, 7];

console.log(stok.length, stok[0] + stok[2]);

Output yang Dihasilkan:

text
3 17

Anotasi number[] berlaku untuk seluruh elemen: memanggil stok.push("empat") ditolak compiler dengan error TS2345 karena argumennya string, bukan number. Tanpa anotasi pun compiler sebenarnya sudah menyimpulkan number[] dari isi array — prinsip inferensi yang kita bahas beberapa bagian di bawah.

Union dan Narrowing #

Kadang satu nilai memang boleh datang dalam lebih dari satu tipe: field stok bisa berupa angka sisa unit, tetapi juga teks status seperti "habis". Union menyatakan hal tersebut dengan tanda |:

ts
function deskripsi(nama: string, stok: number | string): string {
  if (typeof stok === "number") {
    return `${nama}: sisa ${stok} unit`;
  }
  return `${nama}: stok ${stok}`;
}

console.log(deskripsi("Pensil", 25));
console.log(deskripsi("Penghapus", "habis"));

Output yang Dihasilkan:

text
Pensil: sisa 25 unit
Penghapus: stok habis

Mari kita telaah baris-baris kode di atas:

  1. Baris 1 (stok: number | string): parameter kedua menerima number atau string — tanda | dibaca “atau”.
  2. Baris 2-4 (if (typeof stok === "number")): di dalam blok ini compiler tahu stok pasti number, sehingga operasi seperti ${stok} unit aman. Penyempitan tipe di dalam percabangan seperti ini disebut narrowing.
  3. Baris 5 (return ${nama}: stok ${stok}``): jalur setelah if hanya tercapai bila stok bukan number — compiler menyimpulkannya string tanpa anotasi tambahan.

Polongnya sama dengan pengecekan typeof yang sudah biasa kita tulis di JavaScript; bedanya, TypeScript memahami percabangan tersebut dan menyesuaikan tipe di setiap cabang. Narrowing bekerja juga untuk instanceof, pengecekan kesamaan dengan nilai tertentu, dan lainnya — daftar lengkapnya ada pada dokumentasi Narrowing .

Tipe Literal #

Union tidak berhenti di tipe; anggotanya bisa berupa nilai persis. Tipe "atas" | "bawah" hanya menerima dua string itu — string lain ditolak meskipun tetap bertipe string:

ts
let arah: "atas" | "bawah" = "atas";

arah = "kiri";
console.log(arah);

Output yang Dihasilkan:

text
kiri

Node.js menjalankan berkas itu tanpa keluhan dan mencetak "kiri" — sekali lagi, runtime tidak memeriksa tipe. Compiler yang menolak:

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

Output yang Dihasilkan:

text
arah.ts(3,1): error TS2322: Type '"kiri"' is not assignable to type '"atas" | "bawah"'.
kode keluar: 1

Pesan errornya membaca tipe "kiri" secara harfiah — bukan sekadar string — karena nilai tunggal juga merupakan tipe. Tipe literal seperti ini sangat berguna untuk konfigurasi: misalnya parameter level: "info" | "warn" | "error" pada function pencatat log, yang membuat salah ketik "eror" tertangkap sebelum berjalan.

Inferensi Tipe #

TypeScript tidak menuntut anotasi di semua tempat. Ketika variable diinisialisasi langsung dengan nilai, compiler menyimpulkan tipenya dari nilai tersebut dan menegaskannya:

ts
let nama = "Budi";

nama = 25;
console.log(nama);

Output yang Dihasilkan:

text
25

Di mata Node.js tidak ada masalah: let nama = "Budi"; nama = 25; hanyalah variable yang berganti nilai. Namun compiler membacanya berbeda — dari baris pertama, nama disimpulkan bertipe string, sehingga pemberian nilai number di baris kedua adalah pelanggaran:

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

Output yang Dihasilkan:

text
inferensi.ts(3,1): error TS2322: Type 'number' is not assignable to type 'string'.
kode keluar: 1

Inferensi inilah alasan contoh-contoh sebelumnya tetap ringkas: const status = tersedia ? "tersedia" : "habis" langsung disimpulkan string, dan const daftar = ["Budi", "Siti"] disimpulkan string[]. Lalu kapan anotasi tetap ditulis? Parameter function adalah jawaban utamanya — compiler tidak punya nilai awal untuk disimpulkan, jadi tanpa anotasi parameter menjadi any (atau ditolak pada mode ketat, seperti kita lihat pada chapter Strict Mode).

any versus unknown #

Dua tipe khusus menangani nilai yang bentuknya tidak kita ketahui, dengan sikap yang berlawanan. any adalah jalan keluar cepat: nilai bertipe any boleh dipakai untuk apa pun tanpa pemeriksaan — kebebasan yang sama persis dengan JavaScript tanpa TypeScript. unknown adalah padanannya yang disiplin: nilai boleh masuk dari mana pun, tetapi tidak boleh dipakai sebelum tipenya diperiksa:

ts Contoh: any mematikan pemeriksaan
function cetakSembarangan(nilai: any): void {
  console.log(nilai.toFixed(2));
}

Versi any di atas lulus pemeriksaan compiler apa pun yang terjadi — termasuk saat nilai ternyata string dan .toFixed tidak ada. Bandingkan dengan unknown:

ts
function cetak(nilai: unknown): void {
  console.log(nilai.toFixed(2));
}

cetak(42);

Output yang Dihasilkan:

text
42.00

Menjalankan dengan Node.js tampak baik-baik saja — kebetulan argumennya number. Compiler menolak ketidakpastian itu:

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

Output yang Dihasilkan:

text
salah-unknown.ts(2,15): error TS18046: 'nilai' is of type 'unknown'.
kode keluar: 1

Solusinya adalah menyempitkan tipe lebih dulu — pola typeof yang sama dengan union:

ts
function cetak(nilai: unknown): void {
  if (typeof nilai === "number") {
    console.log(nilai.toFixed(2));
  } else {
    console.log(`nilai: ${nilai}`);
  }
}

cetak(42);
cetak("Node.js");

Output yang Dihasilkan:

text
42.00
nilai: Node.js

Di dalam blok if, nilai sudah terbukti number sehingga .toFixed(2) diizinkan; cabang else menangani sisanya. Perhatikan juga return type void pada kedua function — penanda bahwa function tidak mengembalikan nilai apa pun.

Kita kini memiliki kosakata untuk menjelaskan nilai tunggal: primitif, array, union, literal, hingga unknown dengan penyempitannya. Data nyata jaranya lebih besar — object dengan banyak field, bentuk yang boleh diperluas, dan function yang bekerja untuk banyak tipe sekaligus. Semua itu adalah ranah interface dan generic, materi chapter berikutnya; dokumentasi Everyday Types menjadi pendamping yang baik untuk mengulang materi chapter ini.