Mapped Types & Conditional Types

Membuat tipe baru dengan "perulangan" atas kunci (mapped types, modifier + dan -, as), serta tipe bercabang T extends U ? X : Y dan infer untuk mengekstrak tipe.

Part 17 dari 24 ± 55 menit Gratis

Mapped type: perulangan untuk tipe

Di bab 15 kamu memakai Partial, Readonly, dan teman-temannya. Apa isinya? Ternyata hanya mapped type: sebuah "perulangan" atas kunci-kunci sebuah tipe.

type Opsional<T> = {
  [K in keyof T]?: T[K]
}
//  ^^^^^^^^^^^^ "untuk setiap kunci K di T" → buat properti K (opsional) bertipe T[K]

interface Produk { id: number; nama: string }
type ProdukOpsional = Opsional<Produk>      // { id?: number; nama?: string }

Cara membaca [K in keyof T]: ambil semua kunci T, lalu satu per satu jadikan K. Sisi kanan menentukan tipe nilai properti baru.

Mengubah tipe nilai

type Bungkus<T> = { [K in keyof T]: { nilai: T[K] } }
type B = Bungkus<Produk>      // { id: { nilai: number }; nama: { nilai: string } }

type SemuaString<T> = { [K in keyof T]: string }
type Fungsi<T> = { [K in keyof T]: () => T[K] }

Modifier + dan -

Kamu bisa menambah atau membuang readonly dan ?:

type BisaDiubah<T> = { -readonly [K in keyof T]: T[K] }   // buang readonly
type Wajib<T> = { [K in keyof T]-?: T[K] }                 // buang ? (inilah Required)
type TetapSemua<T> = { +readonly [K in keyof T]+?: T[K] }

Mengganti nama kunci dengan as

type DenganGetter<T> = {
  [K in keyof T as `ambil${Capitalize<string & K>}`]: () => T[K]
}
type G = DenganGetter<{ nama: string; umur: number }>
// { ambilNama: () => string; ambilUmur: () => number }

(Bagian `ambil${...}` adalah template literal type — dibahas di bab 18.)

Conditional type: if-else untuk tipe

Bentuknya T extends U ? X : Y — "jika T cocok dengan U, hasilnya X, kalau tidak Y".

type ApakahString<T> = T extends string ? "ya" : "tidak"

type A = ApakahString<string>     // "ya"
type B = ApakahString<number>     // "tidak"
type C = ApakahString<"halo">     // "ya"  ("halo" juga string)

Distributif atas union

Jika T berupa union, conditional type dihitung untuk tiap anggota lalu digabung:

type D = ApakahString<string | number>    // "ya" | "tidak"

type TanpaNull<T> = T extends null | undefined ? never : T
type E = TanpaNull<string | null | undefined>    // string  (inilah NonNullable)

infer: mengekstrak tipe dari dalam

Di dalam bagian extends, infer U berarti "tangkap tipe di posisi ini sebagai variabel U":

type ElemenArray<T> = T extends (infer U)[] ? U : T
type X1 = ElemenArray<number[]>      // number
type X2 = ElemenArray<string>        // string (bukan array → apa adanya)

type KembalianFungsi<F> = F extends (...args: any[]) => infer R ? R : never
type X3 = KembalianFungsi<() => boolean>     // boolean  (inilah ReturnType)

type IsiPromise<T> = T extends Promise<infer U> ? U : T
type X4 = IsiPromise<Promise<string>>     // string

Kapan ini dipakai?

Sebagian besar waktu kamu cukup memakai utility types bawaan. Mapped dan conditional type baru dibutuhkan saat menulis pustaka, utilitas bersama tim, atau tipe yang bergantung pada bentuk data lain. Pahami konsepnya agar tidak takut membaca kode seperti itu.

Saat bingung membaca tipe rumit, pecah jadi bagian kecil, beri nama tiap bagian, dan arahkan kursor di editor untuk melihat hasil akhirnya. Tipe pun sebaiknya dibaca bertahap seperti kode.

Kesalahan umum pemula

  • Membuat tipe sangat rumit untuk masalah sederhana. Tipe yang tidak terbaca adalah utang teknis.
  • Lupa bahwa conditional type menyebar di atas union (bisa membingungkan hasilnya).
  • Mengharapkan conditional type dijalankan saat runtime — ia hanya ada saat kompilasi.

Praktikkan langsung, gratis

Tulis dan jalankan kode langsung di browser, dapatkan penilaian otomatis. Daftar gratis kapan saja untuk menyimpan progres dan meraih sertifikat.

Coba latihan sekarang, tanpa daftar
Bagikan: WhatsApp Facebook LinkedIn X