kodingup.camp

Beranda/TypeScript Mastery

Object, Interface & Type Alias

Modul 5 dari 124 menit bacaObjects, Interfaces and Type Aliases

Isi modul

    Kenapa ini penting

    Hampir semua data di aplikasi nyata berbentuk object: user, produk, pesanan, props komponen, respons API. Mendeskripsikan bentuk object dengan tepat adalah keterampilan TypeScript yang paling sering kamu pakai setiap hari. Begitu bentuknya tertulis, editor tahu isi object itu di mana pun ia dipakai, dan TypeScript menjaga agar tidak ada yang lupa mengisi properti wajib atau menulis properti yang tidak ada.

    Anggap tipe object seperti formulir pendaftaran di kelurahan. Ada kolom wajib (nama, NIK), kolom opsional (email), dan kolom yang tidak boleh diubah setelah dicetak (nomor registrasi). Petugas menolak formulir yang kolom wajibnya kosong. Interface dan type alias adalah "formulir" itu dalam kode.

    Konsep inti

    Bisa langsung (inline) atau diberi nama. Ada dua cara memberi nama: type dan interface.

    // Type alias
    type Produk = {
      id: number;
      nama: string;
      harga: number;
    };
    
    // Interface
    interface Pelanggan {
      id: number;
      nama: string;
      email: string;
    }
    

    Untuk kasus sehari-hari keduanya hampir sama. Kita bahas bedanya di akhir bagian ini.

    Properti opsional dan readonly

    interface Pesanan {
      readonly id: string;     // tidak bisa diubah setelah dibuat
      pelangganId: number;
      catatan?: string;        // opsional: boleh tidak ada
      dibuatPada: Date;
    }
    

    Excess property check

    Saat kamu menulis object literal langsung ke variabel bertipe, TypeScript memeriksa properti yang tidak dikenal. Ini yang menangkap salah ketik seperti hrga di modul pertama. Pemeriksaan ini sengaja hanya untuk object literal segar; object yang sudah tersimpan di variabel lain dinilai berdasarkan "punya properti yang dibutuhkan atau tidak" (structural typing).

    Structural typing

    TypeScript menilai tipe dari bentuknya, bukan dari namanya. Kalau sebuah object punya semua properti yang dibutuhkan Produk, ia dianggap Produk, meskipun tidak pernah dideklarasikan sebagai Produk. Ini berbeda dengan Java atau C#. Analoginya: satpam gedung tidak peduli kamu bekerja di perusahaan mana; ia hanya mengecek apakah kamu membawa kartu akses yang valid.

    Index signature

    Untuk object yang kuncinya dinamis, misalnya kamus terjemahan atau stok per kode barang:

    type StokGudang = { [kodeBarang: string]: number };
    const stok: StokGudang = { "BRG-01": 20, "BRG-02": 5 };
    

    Di modul Utility Types kamu akan bertemu Record<string, number> yang artinya sama dan lebih ringkas.

    Memperluas tipe: extends dan intersection &

    interface Orang {
      nama: string;
    }
    interface Karyawan extends Orang {
      nip: string;
    }
    
    type Alamat = { kota: string };
    type KaryawanLengkap = Karyawan & Alamat; // gabungan: nama + nip + kota
    

    extends (untuk interface) dan & (intersection, untuk type) sama-sama menggabungkan properti. Bedanya, extends langsung melaporkan error kalau properti yang sama punya tipe bertabrakan, sedangkan & menghasilkan tipe yang mustahil dipenuhi (misalnya string & number menjadi never) dan error-nya baru terasa saat dipakai.

    Interface vs type: kapan memakai yang mana

    interface type
    Object Ya Ya
    Union ("a" \| "b"), tuple, primitif, function Tidak Ya
    Memperluas extends &
    Declaration merging (dua deklarasi bernama sama digabung) Ya Tidak (error duplikat)
    Pesan error dan performa compiler pada hierarki besar Sedikit lebih baik Baik

    Pedoman praktis yang dipakai banyak tim:

    Contoh kode

    // toko.ts, memodelkan data toko online
    
    interface Entitas {
      readonly id: number;
      readonly dibuatPada: Date;
    }
    
    interface Produk extends Entitas {
      nama: string;
      harga: number;
      stok: number;
      deskripsi?: string;
      tag: readonly string[];
    }
    
    type Diskon = { persen: number; berlakuSampai: Date };
    type ProdukPromo = Produk & { diskon: Diskon };
    
    const kaos: Produk = {
      id: 1,
      dibuatPada: new Date("2026-10-01"),
      nama: "Kaos Polos",
      harga: 75000,
      stok: 30,
      tag: ["pakaian", "katun"],
    };
    
    const kaosPromo: ProdukPromo = {
      ...kaos,
      diskon: { persen: 20, berlakuSampai: new Date("2026-10-31") },
    };
    
    function hargaAkhir(p: Produk | ProdukPromo): number {
      if ("diskon" in p) return p.harga * (1 - p.diskon.persen / 100);
      return p.harga;
    }
    
    console.log(hargaAkhir(kaos));      // → 75000
    console.log(hargaAkhir(kaosPromo)); // → 60000
    console.log(kaos.deskripsi ?? "(tanpa deskripsi)"); // → (tanpa deskripsi)
    
    // Kesalahan yang ditangkap
    kaos.id = 2; // ❌ error TS2540: Cannot assign to 'id' because it is a read-only property.
    kaos.tag.push("baru"); // ❌ error TS2339: Property 'push' does not exist on type 'readonly string[]'.
    
    const celana: Produk = { // ❌ error TS2741: Property 'stok' is missing in type '{ id: number; dibuatPada: Date; nama: string; harga: number; tag: never[]; }' but required in type 'Produk'.
      id: 2,
      dibuatPada: new Date(),
      nama: "Celana Chino",
      harga: 150000,
      tag: [],
    };
    

    Structural typing dalam praktik:

    // struktural.ts
    type Titik = { x: number; y: number };
    
    function jarakDariNol(t: Titik): number {
      return Math.sqrt(t.x ** 2 + t.y ** 2);
    }
    
    // Object ini tidak pernah dideklarasikan sebagai Titik, tapi bentuknya cocok
    const posisiKursor = { x: 3, y: 4, tombol: "kiri" };
    console.log(jarakDariNol(posisiKursor)); // → 5   (properti ekstra boleh karena bukan literal segar)
    
    console.log(jarakDariNol({ x: 3, y: 4, tombol: "kiri" })); // ❌ error TS2353: Object literal may only specify known properties, and 'tombol' does not exist in type 'Titik'.
    
    // Declaration merging pada interface: menambah properti ke Window
    declare global {
      interface Window {
        versiAplikasi: string;
      }
    }
    window.versiAplikasi = "1.0.0";
    console.log(window.versiAplikasi); // → 1.0.0
    

    Kesalahan umum

    1. Menandai semua properti opsional "biar aman". Akibatnya setiap pemakaian harus memeriksa undefined. Buat properti opsional hanya kalau memang boleh tidak ada.
    2. Mengira readonly membekukan object di runtime. readonly hanya pemeriksaan compile time. Untuk runtime gunakan Object.freeze (dan tipe Readonly<T>).
    3. Bingung kenapa object dengan properti ekstra kadang lolos. Excess property check hanya untuk object literal yang langsung ditulis. Object dari variabel lain dinilai secara struktural.
    4. Mencampur interface dan type tanpa pola. Tidak salah secara teknis, tapi membingungkan tim. Sepakati satu pedoman.
    5. Menduplikasi tipe yang mirip. Kalau ProdukBaru sama dengan Produk tanpa id, jangan ketik ulang semuanya. Turunkan dari Produk dengan Omit (modul Utility Types) atau extends.

    Latihan

    1. Mudah, Buat interface Buku dengan isbn (readonly), judul, penulis, tahun, dan sinopsis opsional. Buat dua object buku.
    2. Sedang, Buat type Kontak = { email: string; telepon?: string } lalu type Siswa = { nama: string; kelas: string } & Kontak. Tulis function kartuSiswa(s: Siswa): string.
    3. Menantang, Buat interface Kendaraan { roda: number; merek: string }, lalu Mobil dan Motor yang extends Kendaraan dengan properti khusus masing-masing (jumlahPintu, jenisMotor). Tulis function deskripsi(k: Mobil | Motor): string yang memakai operator in untuk membedakan keduanya.
    Lihat solusi
    // 1. Mudah
    interface Buku {
      readonly isbn: string;
      judul: string;
      penulis: string;
      tahun: number;
      sinopsis?: string;
    }
    const buku1: Buku = { isbn: "978-602-1", judul: "Laskar Pelangi", penulis: "Andrea Hirata", tahun: 2005 };
    const buku2: Buku = { isbn: "978-602-2", judul: "Bumi Manusia", penulis: "Pramoedya Ananta Toer", tahun: 1980, sinopsis: "Kisah Minke." };
    console.log(buku1.judul, buku2.sinopsis); // → Laskar Pelangi Kisah Minke.
    
    // 2. Sedang
    type Kontak = { email: string; telepon?: string };
    type Siswa = { nama: string; kelas: string } & Kontak;
    
    function kartuSiswa(s: Siswa): string {
      return `${s.nama} (${s.kelas}) - ${s.email}${s.telepon ? " / " + s.telepon : ""}`;
    }
    console.log(kartuSiswa({ nama: "Rina", kelas: "XI IPA 2", email: "rina@sekolah.id" }));
    // → Rina (XI IPA 2) - rina@sekolah.id
    
    // 3. Menantang
    interface Kendaraan { roda: number; merek: string }
    interface Mobil extends Kendaraan { jumlahPintu: number }
    interface Motor extends Kendaraan { jenisMotor: "matic" | "bebek" | "sport" }
    
    function deskripsi(k: Mobil | Motor): string {
      if ("jumlahPintu" in k) return `Mobil ${k.merek}, ${k.jumlahPintu} pintu`;
      return `Motor ${k.merek} jenis ${k.jenisMotor}`;
    }
    console.log(deskripsi({ roda: 4, merek: "Toyota", jumlahPintu: 5 })); // → Mobil Toyota, 5 pintu
    console.log(deskripsi({ roda: 2, merek: "Honda", jenisMotor: "matic" })); // → Motor Honda jenis matic
    

    Ringkasan

    Video pendamping

    Kalau penjelasan di atas belum masuk, tonton ini. Video dari kanal YouTube pihak lain.

    TypeScript Fundamentals - #7 Type Aliases and InterfacesJoy of Code · Bahasa InggrisBuka di YouTube

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.