Template Literal Types

Tipe string berstruktur untuk nama event dan path dengan template literal types.

Daftar isi

Template literal type adalah tipe string yang dibangun dari pola teks berisi sisipan tipe, ditulis dengan tanda kutip terbalik pada posisi tipe. Tempat sisipan ${...} menerima tipe lain, dan setiap anggota union pada sisipan itu menghasilkan satu tipe string tersendiri. Topik ini membahas cara menyusun pola tersebut, penerapannya pada nama event dan jalur arsip, serta pengubah huruf bawaan yang menyertainya.

Menyusun tipe dari potongan string#

Pola pada template literal type bekerja sebagaimana template string pada JavaScript, yaitu teks tetap yang diselingi sisipan ${...}. Perbedaannya, seluruh konstruksi ini berada di posisi tipe dan dihitung oleh compiler, sehingga hasilnya berupa kumpulan bentuk string yang sah dan bukan nilai string. Bila sisipan berupa union, hasilnya berkembang menjadi union berisi setiap kombinasi yang mungkin.

ts nama-event.ts
type Expect<T extends true> = T;
type Equal<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false;

type Perangkat = "suhu" | "lembap";
type Aksi = "mulai" | "berhenti";

type NamaEvent = `${Perangkat}:${Aksi}`;

const bukti: Expect<Equal<NamaEvent, "suhu:mulai" | "suhu:berhenti" | "lembap:mulai" | "lembap:berhenti">> = true;

const penanganan: Record<NamaEvent, boolean> = {
  "suhu:mulai": true,
  "suhu:berhenti": true,
  "lembap:mulai": true,
  "lembap:berhenti": true,
};

console.log(Object.keys(penanganan).length, bukti);
text Keluaran
4 true

Tipe NamaEvent merangkai dua union menjadi empat kombinasi lengkap, yaitu setiap perangkat dipasangkan dengan setiap aksi. Kombinasi semacam ini disebut hasil perkalian kartesian, karena jumlah anggota hasilnya adalah perkalian jumlah anggota kedua sisipan. Bukti Equal menyatakan keempat kombinasi itu secara eksplisit. Karena bukti ini mengikuti hasil pola, setiap perubahan pada Perangkat atau Aksi langsung terlihat pada pemeriksaan tipe.

Pola semacam ini juga sering dipadukan dengan keyof typeof. Menuliskan sisipan berupa keyof typeof TABEL menghasilkan union berisi seluruh kunci objek tersebut, sehingga nama yang diperlukan mengikuti data yang benar-benar ada. Kombinasi keduanya menutup jarak antara nilai dan tipe, sebab kunci yang berubah langsung mengubah hasil polanya.

Objek penanganan menunjukkan manfaat praktisnya, yaitu tabel penanganan yang kuncinya wajib memuat seluruh nama event hasil pola. Kunci yang kurang atau salah eja ditolak compiler, karena tipe kuncinya berasal dari pola dan bukan dari penulisan manual yang rentan salah.

Menegakkan tabel penanganan#

Sifat wajib dari kunci hasil pola baru terasa ketika satu entri hilang. Kekurangan semacam ini mudah terjadi pada tabel yang panjang, karena entri ditulis satu per satu secara manual dan mudah terlewat di tengah daftar. Berkas berikut menuliskan tabel penanganan yang sama tetapi tanpa entri lembap:berhenti, sehingga pemeriksaan tipe menolaknya sebelum program dijalankan.

ts penanganan-salah.ts
// tidak-dijalankan
type Perangkat = "suhu" | "lembap";
type Aksi = "mulai" | "berhenti";

type NamaEvent = `${Perangkat}:${Aksi}`;

const penanganan: Record<NamaEvent, boolean> = {
  "suhu:mulai": true,
  "suhu:berhenti": true,
  "lembap:mulai": true,
};

console.log(penanganan);
bash
# entri lembap:berhenti hilang dari tabel penanganan
tsc --noEmit --pretty false penanganan-salah.ts
text Keluaran
penanganan-salah.ts(7,7): error TS2741: Property '"lembap:berhenti"' is missing in type '{ "suhu:mulai": true; "suhu:berhenti": true; "lembap:mulai": true; }' but required in type 'Record<"lembap:berhenti" | "lembap:mulai" | "suhu:berhenti" | "suhu:mulai", boolean>'.

Pesan error menyebut nama kunci yang hilang dan seluruh daftar kunci yang diharapkan, sehingga perbaikannya tidak memerlukan penelusuran lain. Pola ini menjadikan penambahan varian pada Perangkat atau Aksi aman, karena setiap tabel penanganan yang bergantung pada nama event langsung meminta entri barunya. Manfaat yang sama berlaku ketika sebuah varian dihapus, karena kunci yang tidak lagi dihasilkan pola ditandai sebagai kelebihan yang perlu dibersihkan. Tanpa template literal type, penjagaan semacam ini hanya mungkin lewat daftar string yang ditulis dua kali.

Jalur berstruktur dengan wildcard#

Sisipan tidak harus berupa union tertutup, karena string dan number dapat ditempatkan pada pola sebagai wildcard, yaitu tempat yang mencocokkan teks apa pun pada posisinya. Pola arsip/${number}/${string} berarti string yang dimulai dengan arsip/, diikuti angka, tanda garis miring, dan sisa teks apa pun. Bentuk ini cocok untuk jalur berkas yang strukturnya tetap tetapi isinya bebas.

ts jalur-arsip.ts
type JalurArsip = `arsip/${number}/${string}`;

function bacaJalur(jalur: JalurArsip): string {
  return `membuka ${jalur}`;
}

console.log(bacaJalur("arsip/2026/laporan.csv"));
console.log(bacaJalur("arsip/2025/inventaris.csv"));
text Keluaran
membuka arsip/2026/laporan.csv
membuka arsip/2025/inventaris.csv

Kedua pemanggilan lolos karena kedua jalurnya mengikuti pola tahun dan nama berkas. Parameter fungsi bacaJalur kini menolak string yang strukturnya menyimpang, sebagaimana diperlihatkan berkas berikut dengan jalur yang tidak memuat nama berkasnya.

ts jalur-salah.ts
// tidak-dijalankan
type JalurArsip = `arsip/${number}/${string}`;

function bacaJalur(jalur: JalurArsip): string {
  return `membuka ${jalur}`;
}

console.log(bacaJalur("arsip/2026"));
bash
# jalur tidak memuat bagian nama berkas
tsc --noEmit --pretty false jalur-salah.ts
text Keluaran
jalur-salah.ts(8,23): error TS2345: Argument of type '"arsip/2026"' is not assignable to parameter of type '`arsip/${number}/${string}`'.

Pesan error menampilkan pola lengkapnya, sehingga bagian yang kurang langsung terbaca. Pemeriksaan ini berlaku pada string yang tertulis di kode, sedangkan string yang berasal dari luar program tetap perlu diuraikan saat runtime sebelum dinyatakan sesuai pola. Tipe tersebut menjamin bentuk string pada tempat yang dapat diperiksa compiler, dan bukan pengganti validasi masukan dari luar.

Pengubah huruf bawaan#

TypeScript menyediakan empat tipe bawaan untuk mengubah huruf pada tipe string, yaitu Uppercase, Lowercase, Capitalize, dan Uncapitalize. Keempatnya dapat ditempatkan di dalam sisipan, sehingga pola dapat menyesuaikan bentuk huruf tanpa menuliskan varian huruf kapital secara manual. Bila tidak ada pengubah pada sisipan, hurufnya dipertahankan sebagaimana sumbernya. Pola ini lazim pada penamaan konstanta yang diturunkan dari kunci konfigurasi.

ts huruf-kapital.ts
type Expect<T extends true> = T;
type Equal<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false;

type Kunci = "interval" | "kanal";

type KunciKonstanta = `MODE_${Uppercase<Kunci>}`;

const MODE = {
  MODE_INTERVAL: 5,
  MODE_KANAL: "A2",
} as const;

type KunciMode = keyof typeof MODE;

const bukti1: Expect<Equal<KunciKonstanta, "MODE_INTERVAL" | "MODE_KANAL">> = true;
const bukti2: Expect<Equal<KunciMode, KunciKonstanta>> = true;

console.log(Object.keys(MODE).join(","), bukti1, bukti2);
text Keluaran
MODE_INTERVAL,MODE_KANAL true true

Tipe KunciKonstanta menambahkan awalan MODE_ dan mengubah kunci menjadi huruf kapital melalui Uppercase, sehingga hasilnya mengikuti konvensi penamaan konstanta. Bukti kedua menghubungkan pola tersebut dengan kunci milik konstanta MODE yang diturunkan lewat keyof typeof, dan keduanya dinyatakan setara. Bila kunci konfigurasi bertambah, pola yang sama menghasilkan nama konstanta barunya tanpa penulisan manual.

Kesalahan umum#

Kekeliruan pertama adalah menganggap template literal type memeriksa string yang berasal dari luar kode, misalnya dari argumen baris perintah atau respons layanan. Pemeriksaan hanya berlaku saat kompilasi atas nilai yang tertulis, sehingga string dari luar harus diuraikan terlebih dahulu sebelum diperlakukan sesuai pola. Perbaikannya adalah memisahkan penguraian saat runtime dari pemeriksaan tipe saat kompilasi.

Kekeliruan kedua adalah menumpuk union besar pada beberapa sisipan sekaligus. Jumlah anggota hasil pola merupakan perkalian seluruh sisipannya, sehingga tiga union berisi sepuluh anggota menghasilkan seribu tipe string. Perbaikannya adalah menggunakan wildcard ${string} atau ${number} untuk bagian yang tidak perlu dipersempit, sehingga pola tetap ringkas dan hasilnya tetap dapat dibaca.

Kekeliruan ketiga adalah mengira wildcard ${number} menuntut format angka tertentu. Wildcard tersebut menerima berbagai penulisan angka yang sah, sehingga bentuk yang benar-benar ketat perlu dituliskan sebagai union tipe literal. Mengetahui luas kecocokan wildcard mencegah dugaan berlebihan terhadap pemeriksaan compiler.

Ringkasan#

  • Template literal type menyusun tipe string dari pola teks dengan sisipan tipe, dan union pada sisipan berkembang menjadi semua kombinasinya.
  • Tabel penanganan yang kuncinya berasal dari pola nama event ditertibkan secara lengkap, dan entri yang hilang dilaporkan compiler.
  • Wildcard string dan number pada pola menegakkan struktur jalur, sedangkan isi bebasnya tetap longgar.
  • Pengubah huruf Uppercase, Lowercase, Capitalize, dan Uncapitalize menyusun varian penamaan tanpa menulis ulang daftar kuncinya.

Referensi#