Kenapa ini penting
Di aplikasi nyata kamu jarang hanya butuh satu versi dari sebuah tipe. Ada User lengkap dari database, User tanpa password untuk dikirim ke browser, data form "edit profil" yang semua kolomnya opsional, dan daftar user yang dikelompokkan per peran. Kalau kamu mengetik ulang setiap variasi, tipe-tipe itu akan cepat tidak sinkron: menambah satu properti di User berarti mengingat empat tempat lain.
Utility types adalah "alat transformasi" bawaan TypeScript untuk menurunkan tipe baru dari tipe yang sudah ada. Analoginya seperti fotokopi dengan pengaturan: dari satu dokumen asli, kamu bisa mencetak versi tanpa halaman tertentu (Omit), hanya halaman tertentu (Pick), atau versi draf yang boleh dicoret-coret (Partial). Dokumen aslinya tetap satu, sehingga perubahan cukup dilakukan di satu tempat.
Konsep inti
Operator dasar: keyof, typeof, indexed access
Sebelum utility types, kenali tiga operator yang menjadi bahan bakunya:
type User = { id: number; nama: string; email: string; peran: "admin" | "anggota" };
type KunciUser = keyof User; // "id" | "nama" | "email" | "peran"
type Peran = User["peran"]; // "admin" | "anggota" (indexed access)
type IdAtauNama = User["id" | "nama"]; // number | string
const konfigurasi = { tema: "gelap", bahasa: "id", ukuranFont: 16 };
type Konfigurasi = typeof konfigurasi; // { tema: string; bahasa: string; ukuranFont: number }
keyof Tmenghasilkan union nama properti.T["kunci"]mengambil tipe dari satu properti. Untuk array:T[number]mengambil tipe elemennya.typeof nilai(di posisi tipe) mengambil tipe dari sebuah nilai JavaScript. Ini berbeda dengantypeofdi runtime (typeof x === "string").
Utility types yang paling sering dipakai
| Utility | Hasil | Contoh pemakaian |
|---|---|---|
Partial<T> |
Semua properti jadi opsional | Data form edit / PATCH |
Required<T> |
Semua properti jadi wajib | Konfigurasi setelah diisi default |
Readonly<T> |
Semua properti jadi readonly | State yang tidak boleh dimutasi |
Pick<T, K> |
Hanya properti K |
Kartu ringkas: Pick<User, "id" \| "nama"> |
Omit<T, K> |
Semua kecuali K |
Data baru tanpa id: Omit<User, "id"> |
Record<K, V> |
Object dengan kunci K dan nilai V |
Record<Peran, string[]> |
Exclude<U, X> |
Anggota union U yang bukan X |
Exclude<Status, "dibatalkan"> |
Extract<U, X> |
Anggota union U yang cocok dengan X |
Ambil anggota discriminated union |
NonNullable<T> |
T tanpa null dan undefined |
Setelah validasi |
ReturnType<F> |
Tipe kembalian function F |
Tipe dari function factory |
Parameters<F> |
Tuple tipe parameter F |
Membungkus function lain |
Awaited<T> |
Isi dari Promise | Awaited<ReturnType<typeof ambilUser>> |
Menggabungkan utility types
Kekuatan sebenarnya muncul saat digabung:
type User = { id: number; nama: string; email: string; peran: "admin" | "anggota" };
// Data untuk membuat user baru: tanpa id, tapi peran opsional
type UserBaru = Omit<User, "id" | "peran"> & Partial<Pick<User, "peran">>;
ReturnType dan Awaited untuk function async
Ini pola yang sering kamu temui ketika tipe data "lahir" dari function, bukan ditulis manual:
async function ambilProfil() {
return { id: 1, nama: "Sari", poin: 120 };
}
type Profil = Awaited<ReturnType<typeof ambilProfil>>; // { id: number; nama: string; poin: number }
Baca dari dalam ke luar: typeof ambilProfil (tipe function), ReturnType<...> (Promise<{...}>), Awaited<...> (isi Promise).
Mapped type (sekilas)
Semua utility di atas dibangun dengan mapped type, yaitu "loop" di level tipe. Kamu tidak wajib menulisnya sendiri, tapi berguna untuk dipahami:
type JadiString<T> = { [K in keyof T]: string };
// Untuk setiap kunci K di T, buat properti K bertipe string
Partial<T> sendiri didefinisikan kurang lebih sebagai { [K in keyof T]?: T[K] }.
Contoh kode
// utility.ts, mengelola data user di aplikasi admin
interface User {
id: number;
nama: string;
email: string;
password: string;
peran: "admin" | "editor" | "anggota";
dibuatPada: Date;
}
// 1. Omit: data yang aman dikirim ke browser (tanpa password)
type UserPublik = Omit<User, "password">;
// 2. Pick: kartu ringkas di daftar
type KartuUser = Pick<User, "id" | "nama" | "peran">;
// 3. Partial: data form edit, hanya kolom yang diubah
type EditUser = Partial<Omit<User, "id" | "dibuatPada">>;
// 4. Record: hak akses per peran
const hakAkses: Record<User["peran"], string[]> = {
admin: ["baca", "tulis", "hapus"],
editor: ["baca", "tulis"],
anggota: ["baca"],
};
function keKartu(u: User): KartuUser {
return { id: u.id, nama: u.nama, peran: u.peran };
}
function perbarui(u: User, perubahan: EditUser): User {
return { ...u, ...perubahan };
}
function sembunyikanPassword({ password, ...sisa }: User): UserPublik {
return sisa;
}
const sari: User = {
id: 1,
nama: "Sari",
email: "sari@contoh.id",
password: "rahasia123",
peran: "editor",
dibuatPada: new Date("2026-01-15"),
};
console.log(keKartu(sari)); // → { id: 1, nama: 'Sari', peran: 'editor' }
console.log(perbarui(sari, { peran: "admin" }).peran); // → admin
console.log(hakAkses[sari.peran]); // → [ 'baca', 'tulis' ]
console.log("password" in sembunyikanPassword(sari)); // → false
// Kesalahan yang ditangkap
perbarui(sari, { id: 99 }); // ❌ error TS2353: Object literal may only specify known properties, and 'id' does not exist in type 'Partial<Omit<User, "dibuatPada" | "id">>'.
const bocor: UserPublik = sembunyikanPassword(sari);
console.log(bocor.password); // ❌ error TS2339: Property 'password' does not exist on type 'UserPublik'.
// Record memaksa semua peran diisi
const labelPeran: Record<User["peran"], string> = { // ❌ error TS2741: Property 'anggota' is missing in type '{ admin: string; editor: string; }' but required in type 'Record<"admin" | "anggota" | "editor", string>'.
admin: "Administrator",
editor: "Editor",
};
Tipe turunan dari function dan konstanta:
// turunan.ts
async function ambilKeranjang(userId: number) {
return {
userId,
items: [{ produkId: 7, jumlah: 2 }],
total: 150000,
};
}
type Keranjang = Awaited<ReturnType<typeof ambilKeranjang>>;
type ItemKeranjang = Keranjang["items"][number]; // { produkId: number; jumlah: number }
type ArgumenAmbil = Parameters<typeof ambilKeranjang>; // [userId: number]
function ringkas(k: Keranjang): string {
return `${k.items.length} jenis barang, total Rp${k.total.toLocaleString("id-ID")}`;
}
const item: ItemKeranjang = { produkId: 1, jumlah: 3 };
const arg: ArgumenAmbil = [42];
ambilKeranjang(...arg).then((k) => console.log(ringkas(k), item.jumlah));
// → 1 jenis barang, total Rp150.000 3
// Union dari konstanta dengan as const + typeof + indexed access
const METODE_BAYAR = ["transfer", "ewallet", "cod"] as const;
type MetodeBayar = (typeof METODE_BAYAR)[number]; // "transfer" | "ewallet" | "cod"
type BayarOnline = Exclude<MetodeBayar, "cod">; // "transfer" | "ewallet"
const pilihan: BayarOnline = "ewallet";
console.log(METODE_BAYAR.includes(pilihan)); // → true
Perhatikan: kalau besok ambilKeranjang mengembalikan properti baru ongkir, tipe Keranjang otomatis ikut berubah tanpa kamu sentuh.
Satu detail kecil dari TypeScript 7: urutan anggota union di pesan error ditampilkan dalam urutan yang stabil, sehingga bisa berbeda dari urutan yang kamu tulis. Contohnya, Omit<User, "id" | "dibuatPada"> muncul di pesan error sebagai Omit<User, "dibuatPada" | "id">. Artinya sama persis; jangan bingung saat membandingkan pesan error dengan kodemu.
Kesalahan umum
- Menyalin ulang tipe yang mirip. Kalau
UserPublikdiketik ulang tanpapassword, laluUsermendapat properti baru, keduanya tidak sinkron. Turunkan denganOmit/Pick. - Memakai
Partialterlalu luas.Partial<User>untuk data yang sebenarnya wajib membuat setiap pemakaian harus mengecekundefined. Pakai hanya untuk data yang memang sebagian (form edit, PATCH). - Bingung
typeofdi posisi tipe vs runtime.type X = typeof objmengambil tipe dari nilai.typeof x === "string"adalah pemeriksaan runtime JavaScript. - Lupa
Awaiteduntuk function async.ReturnType<typeof fnAsync>masihPromise<...>. Bungkus denganAwaiteduntuk isinya. Omitdengan salah ketik nama kunci.Omit<User, "pasword">tidak error (karenaOmitmenerima string apa saja) dan password tetap ada. Periksa hasilnya dengan hover di editor.
Latihan
- Mudah, Dari
type Artikel = { id: number; judul: string; isi: string; terbit: boolean }, buatArtikelBaru(tanpaid) danPratinjauArtikel(hanyaiddanjudul). - Sedang, Buat
type Bahasa = "id" | "en"dan objectteks: Record<Bahasa, { sapa: string; pamit: string }>. Tulis functionterjemah(bahasa: Bahasa, kunci: keyof (typeof teks)["id"]): string. - Menantang, Tulis function
async function muatDashboard()yang mengembalikan{ pengguna: number; pendapatan: number; terlaris: string[] }. Turunkan tipeDashboarddenganAwaited<ReturnType<...>>, lalu buattype KartuStatistik = { [K in keyof Dashboard]: { label: string; nilai: Dashboard[K] } }.
Lihat solusi
// 1. Mudah
type Artikel = { id: number; judul: string; isi: string; terbit: boolean };
type ArtikelBaru = Omit<Artikel, "id">;
type PratinjauArtikel = Pick<Artikel, "id" | "judul">;
const draf: ArtikelBaru = { judul: "Belajar TS", isi: "...", terbit: false };
const pratinjau: PratinjauArtikel = { id: 1, judul: "Belajar TS" };
console.log(draf.terbit, pratinjau.judul); // → false Belajar TS
// 2. Sedang
type Bahasa = "id" | "en";
const teks: Record<Bahasa, { sapa: string; pamit: string }> = {
id: { sapa: "Halo", pamit: "Sampai jumpa" },
en: { sapa: "Hello", pamit: "Goodbye" },
};
function terjemah(bahasa: Bahasa, kunci: keyof (typeof teks)["id"]): string {
return teks[bahasa][kunci];
}
console.log(terjemah("en", "pamit")); // → Goodbye
// 3. Menantang
async function muatDashboard() {
return { pengguna: 1250, pendapatan: 48_500_000, terlaris: ["Kopi Susu", "Croissant"] };
}
type Dashboard = Awaited<ReturnType<typeof muatDashboard>>;
type KartuStatistik = { [K in keyof Dashboard]: { label: string; nilai: Dashboard[K] } };
muatDashboard().then((d) => {
const kartu: KartuStatistik = {
pengguna: { label: "Pengguna aktif", nilai: d.pengguna },
pendapatan: { label: "Pendapatan", nilai: d.pendapatan },
terlaris: { label: "Produk terlaris", nilai: d.terlaris },
};
console.log(kartu.terlaris.nilai.join(", ")); // → Kopi Susu, Croissant
});
Ringkasan
keyof T(union kunci),T["k"]/T[number](indexed access), dantypeof nilaiadalah bahan dasar tipe turunan.Partial,Required,Readonlymengubah sifat semua properti;PickdanOmitmemilih properti.Record<K, V>membuat object dengan kunci tertentu dan memaksa semua kunci diisi.ReturnType,Parameters, danAwaitedmenurunkan tipe dari function, sehingga tipe ikut berubah otomatis.- Satu tipe sumber, banyak turunan: perubahan cukup di satu tempat.