Sintaks Utama TypeScript #

Setiap bahasa pemrograman punya idiom dan konstruksi yang membentuk cara berpikir penggunanya. TypeScript bukan pengecualian — tapi karena ia dibangun di atas JavaScript, ada lapisan tambahan yang perlu kamu pahami: TypeScript tidak mengganti sintaks JavaScript, ia memperluas sintaks tersebut. Kamu menulis JavaScript seperti biasa, tapi dengan anotasi tipe yang memberi compiler informasi untuk memvalidasi kode sebelum dijalankan. Artikel ini membahas konstruksi sintaks yang paling sering kamu temui saat menulis TypeScript — dari anotasi tipe dasar, fungsi, interface, class, hingga fitur lanjutan seperti generics dan decorator.

Anotasi Tipe #

Anotasi tipe adalah sintaks paling mendasar yang membedakan TypeScript dari JavaScript. Kamu menambahkan tipe setelah nama variabel, parameter, atau return value menggunakan tanda titik dua (:). Ini adalah cara kamu “berbicara” dengan compiler TypeScript.

// Anotasi tipe dasar pada variabel
let selesai: boolean = false;
let usia: number = 30;
let nama: string = "Budi";
let daftar: number[] = [1, 2, 3, 4, 5];

// Alternatif sintaks untuk array menggunakan Generic
let daftarLain: Array<string> = ["a", "b", "c"];

Anotasi tipe bersifat opsional dalam banyak kasus — TypeScript bisa menyimpulkan tipe secara otomatis melalui mekanisme type inference. Tapi ada situasi di mana anotasi eksplisit lebih aman dan lebih jelas:

// ANTI-PATTERN: Mendeklarasikan variabel tanpa nilai awal tanpa anotasi tipe
// TypeScript menyimpulkan tipe 'any', kehilangan manfaat type safety
let nilai;
nilai = 42;
nilai = "teks"; // tidak ada error, padahal ini mungkin bug

// BENAR: Deklarasikan tipe saat variabel belum memiliki nilai awal
let nilaiBenar: number;
nilaiBenar = 42;
nilaiBenar = "teks"; // ✗ Error: Type 'string' is not assignable to type 'number'

TypeScript menyediakan tipe-tipe primitif berikut yang sering digunakan:

TipeDeskripsiContoh Nilai
booleanNilai logikatrue, false
numberSemua angka (integer & float)42, 3.14, -7
stringTeks"halo", `template`
nullKetiadaan nilai yang disengajanull
undefinedNilai yang belum ditetapkanundefined
anyMenonaktifkan type checkingNilai apapun
unknownTipe aman pengganti anyNilai apapun (harus di-narrow)
neverNilai yang tidak pernah adaFungsi yang selalu throw
voidTidak ada return valueReturn fungsi tanpa nilai

Fungsi dengan Tipe #

Fungsi di TypeScript bisa dan harus memiliki anotasi tipe pada parameter dan nilai kembaliannya. Ini memastikan fungsi digunakan dengan benar — compiler akan menolak pemanggilan dengan argumen yang salah tipe.

// Fungsi dengan tipe parameter dan return type eksplisit
function salam(nama: string): string {
  return `Halo, ${nama}!`;
}

console.log(salam("Budi")); // ✓ Output: Halo, Budi!
// salam(123);              // ✗ Error: Argument of type 'number' is not assignable to parameter of type 'string'

Parameter Opsional dan Default #

TypeScript mendukung parameter opsional (dengan ?) dan parameter dengan nilai default:

// Parameter opsional menggunakan tanda '?'
function buatProfil(nama: string, usia?: number): string {
  if (usia !== undefined) {
    return `${nama}, ${usia} tahun`;
  }
  return nama;
}

// Parameter dengan nilai default
function hitungDiskon(harga: number, diskon: number = 0.1): number {
  return harga * (1 - diskon);
}

console.log(buatProfil("Budi"));        // "Budi"
console.log(buatProfil("Budi", 25));    // "Budi, 25 tahun"
console.log(hitungDiskon(100000));       // 90000
console.log(hitungDiskon(100000, 0.2)); // 80000

Arrow Function #

Arrow function JavaScript juga mendukung anotasi tipe TypeScript:

// Arrow function dengan tipe eksplisit
const kali = (a: number, b: number): number => a * b;

// Tipe bisa dideklarasikan terpisah dari implementasi
type FungsiMath = (x: number, y: number) => number;

const tambah: FungsiMath = (a, b) => a + b;
const kurang: FungsiMath = (a, b) => a - b;

Interface #

Interface mendefinisikan “kontrak” — bentuk yang harus dipenuhi oleh sebuah object. Ini adalah cara paling ekspresif untuk mendokumentasikan struktur data di TypeScript. Tidak seperti class, interface tidak menghasilkan kode JavaScript apapun saat dikompilasi — ia murni eksistensi di level TypeScript.

interface Pengguna {
  id: number;
  nama: string;
  email: string;
  dibuat: Date;
}

// Object harus memenuhi semua properti yang diwajibkan interface
const pengguna: Pengguna = {
  id: 1,
  nama: "Budi Santoso",
  email: "[email protected]",
  dibuat: new Date(),
};

Properti Opsional dan Readonly #

Interface mendukung properti opsional (dengan ?) dan properti yang tidak boleh diubah setelah inisialisasi (dengan readonly):

interface Produk {
  readonly id: number;   // tidak bisa diubah setelah dibuat
  nama: string;
  harga: number;
  deskripsi?: string;   // opsional — boleh tidak ada
  stok?: number;        // opsional
}

const produk: Produk = {
  id: 101,
  nama: "Laptop",
  harga: 12000000,
};

// produk.id = 999; // ✗ Error: Cannot assign to 'id' because it is a read-only property

Interface untuk Fungsi #

Interface juga bisa mendefinisikan bentuk fungsi:

interface FungsiValidator {
  (nilai: string): boolean;
}

const validasiEmail: FungsiValidator = (email) => {
  return email.includes("@");
};

const validasiNama: FungsiValidator = (nama) => {
  return nama.length >= 2;
};

Perluasan Interface (Extends) #

Interface bisa diperluas dari interface lain, membangun hierarki tipe yang terstruktur:

interface Entitas {
  id: number;
  dibuat: Date;
  diperbarui: Date;
}

interface Pengguna extends Entitas {
  nama: string;
  email: string;
}

interface Admin extends Pengguna {
  level: number;
  izin: string[];
}

Class #

Class TypeScript adalah class JavaScript modern dengan tambahan anotasi tipe, access modifier, dan fitur OOP lainnya. Class menghasilkan kode JavaScript nyata saat dikompilasi — berbeda dengan interface.

class Hewan {
  // Properti dengan access modifier
  private nama: string;
  protected jenis: string;
  public aktif: boolean;

  constructor(nama: string, jenis: string) {
    this.nama = nama;
    this.jenis = jenis;
    this.aktif = true;
  }

  // Metode publik
  bunyikan(): void {
    console.log(`${this.nama} membuat suara.`);
  }

  // Getter
  get namaPanjang(): string {
    return `${this.jenis}: ${this.nama}`;
  }
}

const anjing = new Hewan("Rex", "Anjing");
anjing.bunyikan();              // "Rex membuat suara."
console.log(anjing.namaPanjang); // "Anjing: Rex"
// console.log(anjing.nama);    // ✗ Error: Property 'nama' is private

Pewarisan Class #

class Kucing extends Hewan {
  private warnaBulu: string;

  constructor(nama: string, warnaBulu: string) {
    super(nama, "Kucing"); // Panggil constructor parent
    this.warnaBulu = warnaBulu;
  }

  // Override metode parent
  bunyikan(): void {
    console.log(`${this.jenis} ini mengucapkan: Meow!`);
  }

  info(): string {
    return `Kucing berbulu ${this.warnaBulu}`;
  }
}

const kucing = new Kucing("Mochi", "oranye");
kucing.bunyikan(); // "Kucing ini mengucapkan: Meow!"

Shorthand Constructor Parameter #

TypeScript memiliki sintaks ringkas untuk mendeklarasikan dan menginisialisasi properti sekaligus di constructor:

// ANTI-PATTERN: Verbose — deklarasi dan inisialisasi terpisah
class ProdukVerbose {
  nama: string;
  harga: number;

  constructor(nama: string, harga: number) {
    this.nama = nama;
    this.harga = harga;
  }
}

// BENAR: Shorthand — deklarasikan langsung di parameter constructor
class Produk {
  constructor(
    public nama: string,
    public harga: number,
    private stok: number = 0
  ) {}

  tersedia(): boolean {
    return this.stok > 0;
  }
}

Union Type dan Type Alias #

Union type memungkinkan sebuah nilai memiliki lebih dari satu tipe yang mungkin. Type alias memberi nama pada tipe yang kompleks agar bisa digunakan kembali. Keduanya adalah alat paling sering dipakai untuk mengekspresikan kefleksibelan tipe di TypeScript.

// Union type — nilai bisa string ATAU number
let id: string | number;
id = "usr-123"; // ✓
id = 456;       // ✓
// id = true;   // ✗ Error: Type 'boolean' is not assignable to type 'string | number'

// Type alias untuk union type yang sering dipakai
type ID = string | number;
type Status = "aktif" | "nonaktif" | "pending";
type HasilOperasi = "sukses" | "gagal";

let statusPengguna: Status = "aktif";
// statusPengguna = "diblokir"; // ✗ Error: tidak termasuk union yang valid

Narrowing — Menyempitkan Union Type #

Saat bekerja dengan union type, kamu perlu melakukan narrowing sebelum menggunakan metode yang spesifik untuk satu tipe:

function prosesId(id: string | number): string {
  // ANTI-PATTERN: Langsung pakai metode tanpa narrowing
  // return id.toUpperCase(); // ✗ Error: 'toUpperCase' tidak ada di 'number'

  // BENAR: Narrow terlebih dahulu
  if (typeof id === "string") {
    return id.toUpperCase(); // TypeScript tahu id adalah string di sini
  }
  return id.toString(); // TypeScript tahu id adalah number di sini
}

Enum #

Enum mendefinisikan kumpulan konstanta yang bernama. Ini lebih ekspresif dibanding menggunakan angka atau string magis yang tersebar di seluruh kode.

// Numeric enum — nilai dimulai dari 0 secara default
enum Arah {
  Atas,    // 0
  Bawah,   // 1
  Kiri,    // 2
  Kanan,   // 3
}

let arahKarakter: Arah = Arah.Atas;
console.log(arahKarakter); // 0

// String enum — nilai lebih deskriptif dan mudah di-debug
enum StatusPesanan {
  Menunggu = "MENUNGGU",
  Diproses = "DIPROSES",
  Dikirim  = "DIKIRIM",
  Selesai  = "SELESAI",
  Dibatalkan = "DIBATALKAN",
}

function prosesStatusPesanan(status: StatusPesanan): void {
  switch (status) {
    case StatusPesanan.Menunggu:
      console.log("Pesanan menunggu konfirmasi");
      break;
    case StatusPesanan.Dikirim:
      console.log("Pesanan sedang dalam pengiriman");
      break;
    default:
      console.log(`Status: ${status}`);
  }
}
Untuk string enum vs union type literal, keduanya sering bisa saling menggantikan. Union type literal (type Status = "aktif" | "nonaktif") umumnya lebih disukai karena lebih ringan (tidak menghasilkan kode JS tambahan) dan lebih mudah dikomposisi. Gunakan enum saat kamu butuh nilai numerik otomatis atau saat enum perlu diiterasi.

Generics #

Generics adalah mekanisme untuk membuat komponen yang bekerja dengan berbagai tipe tanpa kehilangan informasi tipe tersebut. Ini adalah fitur yang sangat powerful — dasar dari banyak abstraksi di library TypeScript.

// Fungsi generik — tipe T adalah "placeholder" yang diisi saat pemanggilan
function identitas<T>(nilai: T): T {
  return nilai;
}

// TypeScript menyimpulkan tipe dari argumen
console.log(identitas("halo"));  // tipe: string
console.log(identitas(42));      // tipe: number
console.log(identitas(true));    // tipe: boolean

// Atau beri tipe eksplisit
console.log(identitas<string>("eksplisit"));

Generic pada Class dan Interface #

// Stack generik — bekerja untuk tipe apapun
class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }

  kosong(): boolean {
    return this.items.length === 0;
  }
}

const stackAngka = new Stack<number>();
stackAngka.push(1);
stackAngka.push(2);
stackAngka.push(3);
console.log(stackAngka.pop()); // 3

const stackTeks = new Stack<string>();
stackTeks.push("pertama");
stackTeks.push("kedua");

Generic Constraint #

Kamu bisa membatasi tipe apa saja yang boleh digunakan sebagai type parameter:

// T harus memiliki properti 'panjang'
interface PunyaPanjang {
  panjang: number;
}

function cetakPanjang<T extends PunyaPanjang>(nilai: T): void {
  console.log(`Panjang: ${nilai.panjang}`);
}

cetakPanjang("teks");          // ✓ string punya .length
cetakPanjang([1, 2, 3]);       // ✓ array punya .length
// cetakPanjang(42);            // ✗ number tidak punya .length

Modules #

TypeScript menggunakan sistem modul ES yang sama dengan JavaScript modern — export untuk mengekspos dan import untuk menggunakan. Setiap file .ts adalah modul sendiri.

// src/utils/format.ts
export function formatMata(jumlah: number): string {
  return new Intl.NumberFormat("id-ID", {
    style: "currency",
    currency: "IDR",
  }).format(jumlah);
}

export function formatTanggal(tanggal: Date): string {
  return new Intl.DateTimeFormat("id-ID").format(tanggal);
}

// Export default untuk export utama modul
export default function formatAngka(nilai: number): string {
  return nilai.toLocaleString("id-ID");
}
// src/index.ts
// Named import
import { formatMata, formatTanggal } from "./utils/format";

// Default import
import formatAngka from "./utils/format";

// Import semua dengan alias namespace
import * as Format from "./utils/format";

console.log(formatMata(150000));           // "Rp 150.000"
console.log(Format.formatMata(200000));    // "Rp 200.000"

Tuple #

Tuple adalah array dengan jumlah elemen yang tetap dan tipe yang berbeda-beda per posisi. Berbeda dari array biasa yang semua elemennya bertipe sama.

// Array biasa — semua elemen bertipe sama
const daftarAngka: number[] = [1, 2, 3];

// Tuple — tipe per posisi berbeda dan tetap
let pengguna: [string, number, boolean] = ["Budi", 25, true];

// Akses elemen tuple
console.log(pengguna[0]); // string: "Budi"
console.log(pengguna[1]); // number: 25
console.log(pengguna[2]); // boolean: true

// Destructuring tuple
const [nama, usia, aktif] = pengguna;
console.log(`${nama} berusia ${usia} tahun`);

Tuple sebagai Return Value Fungsi #

Tuple sangat berguna untuk fungsi yang perlu mengembalikan lebih dari satu nilai dengan tipe berbeda:

function bagi(pembilang: number, penyebut: number): [number, string] {
  if (penyebut === 0) {
    return [0, "Error: tidak bisa dibagi nol"];
  }
  return [pembilang / penyebut, "sukses"];
}

const [hasil, pesan] = bagi(10, 3);
console.log(`Hasil: ${hasil}, Status: ${pesan}`);
// "Hasil: 3.3333333333333335, Status: sukses"

Optional Chaining dan Nullish Coalescing #

Dua operator modern ini menangani masalah yang sangat umum: mengakses properti dari nilai yang mungkin null atau undefined. Sebelum operator ini ada, kode penanganan null sangat verbose.

interface Alamat {
  kota: string;
  provinsi: string;
  kodePos?: string;
}

interface Profil {
  nama: string;
  alamat?: Alamat;
  telepon?: string;
}

const profil: Profil = {
  nama: "Budi Santoso",
  // alamat tidak diisi — undefined
};

// ANTI-PATTERN: Pengecekan manual yang verbose
// if (profil && profil.alamat && profil.alamat.kota) {
//   console.log(profil.alamat.kota);
// }

// BENAR: Optional chaining (?.) — aman dan ringkas
console.log(profil.alamat?.kota);         // undefined (bukan error)
console.log(profil.alamat?.kodePos);      // undefined

// Nullish coalescing (??) — nilai fallback jika null/undefined
const kota = profil.alamat?.kota ?? "Kota tidak diketahui";
const telepon = profil.telepon ?? "Tidak ada telepon";

console.log(kota);    // "Kota tidak diketahui"
console.log(telepon); // "Tidak ada telepon"

Perbedaan ?? vs || #

Ini sering menjadi sumber bug yang halus:

// '||' menggunakan falsy check — 0, "", false dianggap falsy
const stok = 0;
console.log(stok || 10); // 10 — SALAH! 0 adalah stok valid, bukan "tidak ada nilai"

// '??' hanya menggantikan null dan undefined — jauh lebih presisi
console.log(stok ?? 10); // 0 — BENAR! 0 adalah stok yang valid

Decorator #

Decorator adalah fitur eksperimental (tapi sudah stabil di TypeScript 5.x) yang memungkinkan kamu menambahkan metadata atau memodifikasi perilaku class, metode, atau properti. Decorator sering digunakan di framework seperti NestJS dan Angular.

// Decorator sederhana untuk logging
function logMetode(target: any, namaMetode: string, deskripsi: PropertyDescriptor) {
  const metodeSumber = deskripsi.value;

  deskripsi.value = function (...args: any[]) {
    console.log(`Memanggil ${namaMetode} dengan argumen:`, args);
    const hasil = metodeSumber.apply(this, args);
    console.log(`${namaMetode} mengembalikan:`, hasil);
    return hasil;
  };

  return deskripsi;
}

class KalkulatorHarga {
  @logMetode
  hitungTotal(harga: number, kuantitas: number): number {
    return harga * kuantitas;
  }
}

const kalkulator = new KalkulatorHarga();
kalkulator.hitungTotal(50000, 3);
// Log: Memanggil hitungTotal dengan argumen: [50000, 3]
// Log: hitungTotal mengembalikan: 150000
Untuk menggunakan decorator, aktifkan opsi experimentalDecorators: true di tsconfig.json. Di TypeScript 5.x, ada dua “versi” decorator — decorator lama (stage 2) dan decorator baru (stage 3) yang perilakunya berbeda. Pastikan kamu tahu versi mana yang digunakan framework yang kamu pakai.

Peta Konsep Sintaks TypeScript #

Semua konstruksi sintaks yang dibahas di artikel ini saling berkaitan. Berikut gambaran bagaimana mereka terhubung:

flowchart TD
    A[TypeScript Syntax] --> B[Tipe Dasar]
    A --> C[Struktur Data]
    A --> D[Abstraksi]
    A --> E[Operator Modern]

    B --> B1[boolean, number, string]
    B --> B2[null, undefined, any, unknown]
    B --> B3[Union Type]
    B --> B4[Type Alias]

    C --> C1[Array]
    C --> C2[Tuple]
    C --> C3[Enum]

    D --> D1[Interface]
    D --> D2[Class]
    D --> D3[Generics]
    D --> D4[Modules]
    D --> D5[Decorator]

    E --> E1[Optional Chaining ?.] 
    E --> E2[Nullish Coalescing ??]

    D1 --> D2
    D3 --> D1
    D3 --> D2

Ringkasan #

  • Anotasi tipe — tambahkan tipe setelah nama menggunakan :, dan aktifkan strict: true agar compiler benar-benar memvalidasi semua tipe termasuk null dan undefined.
  • Union type (string | number) — gunakan saat nilai bisa memiliki lebih dari satu tipe; selalu lakukan narrowing sebelum menggunakan metode spesifik tipe.
  • Interface vs Type Alias — keduanya mendefinisikan bentuk object; interface lebih baik untuk hierarki objek dan bisa di-extend, type alias lebih fleksibel untuk union dan intersection.
  • Class dengan access modifier — gunakan private, protected, public untuk enkapsulasi; manfaatkan shorthand constructor parameter untuk mengurangi boilerplate.
  • Generics — tulis komponen sekali, gunakan untuk banyak tipe; gunakan constraint (extends) untuk membatasi tipe yang valid.
  • Enum — cocok untuk konstanta bernama yang perlu diiterasi atau memiliki nilai numerik; untuk literal string sederhana, union type literal lebih disukai.
  • Optional chaining (?.) — akses properti bertingkat dengan aman tanpa null check manual yang verbose.
  • Nullish coalescing (??) — lebih presisi dari || untuk nilai fallback karena hanya menggantikan null dan undefined, bukan semua nilai falsy seperti 0 atau "".
  • Decorator — aktifkan experimentalDecorators di tsconfig; berguna untuk logging, validasi, dan metadata di framework seperti NestJS.

← Sebelumnya: Instalasi   Berikutnya: Komentar →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact