Typescript Advanced Types Unions Intersections Mapped Conditional Infer Type Guards
Selesai membaca dokumentasi ini?
Kembali ke rujukan utama TypeScript untuk melanjutkan topik lainnya.
Kembali ke rujukan utama TypeScript untuk melanjutkan topik lainnya.
Advanced types di TypeScript dipakai untuk membentuk tipe yang lebih ekspresif dari sekadar object shape dasar. Di sini, tipe bukan cuma dipakai untuk memberi label struktur data, tapi juga untuk menggabungkan, memetakan, menyaring, dan menurunkan tipe lain secara komposisional.
Lima konsep yang paling sering muncul dalam kerja harian adalah:
infer: mengekstrak bagian dari tipe lain di dalam conditional type.Kalau dipakai dengan tepat, keenamnya membantu membuat tipe yang:
Pakai advanced types saat tipe yang kamu butuhkan tidak bisa dijelaskan dengan bentuk statis yang sederhana.
Contoh umum:
type RequestState = 'idle' | 'loading' | 'success' | 'error';Contoh umum:
type WithId = { id: string };
type WithTimestamps = { createdAt: string; updatedAt: string };
type UserRecord = WithId & WithTimestamps;infer kalauunknown,Inti kekuatan advanced types ada di komposisi. Satu tipe bisa menjadi bahan baku untuk tipe lain, lalu hasilnya dipakai lagi sebagai input.
Pola ini penting karena kamu tidak perlu menulis ulang bentuk data yang sama di banyak tempat. Yang berubah cukup satu sumber kebenaran, lalu tipe turunan mengikuti.
Union dipakai saat sebuah nilai bisa berupa salah satu dari beberapa tipe.
type Input = string | number;any,type Loading = { status: 'loading' };
type Success = { status: 'success'; data: string[] };
type ErrorState = { status: 'error'; message: string };
Discriminated union biasanya lebih baik daripada union biasa karena TypeScript bisa mengenali pembeda yang stabil, misalnya status.
Kalau union mulai terasa berat, biasanya kamu perlu discriminator yang lebih rapi atau pecah model domain-nya.
Intersection A & B berarti nilai harus memenuhi semua sisi sekaligus.
type Entity = { id: string } & { createdAt: string };type ApiMeta = {
requestId: string;
receivedAt: string;
};
type User = {
id: string;
name: string;
};
type UserResponse =Mapped types membentuk tipe baru dengan iterasi atas property keys dari tipe lain.
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};readonly, partial, atau nullable dari model,type FormState<T> = {
[K in keyof T]: {
value: T[K];
touched: boolean;
error?: string;
};
};Dengan pola ini, kamu cukup mendefinisikan model inti sekali, lalu form state mengikuti otomatis.
type DeepReadonlyish<T> = {
readonly [K in keyof T]?: T[K];
};Kamu bisa menambah atau menghapus modifier seperti readonly dan ? untuk membangun variasi tipe yang lebih spesifik.
Conditional types memakai bentuk:
type Result<T> = T extends SomeType ? A : B;type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<123>; // falseSalah satu perilaku penting: conditional types sering terdistribusi ke union.
type ToArray<T> = T extends any ? T[] : never;
type X = ToArray<string | number>; // string[] | number[]Ini berguna, tapi juga sering bikin hasil akhir berbeda dari dugaan awal.
Kalau logic-nya mulai rumit, pecah menjadi beberapa alias kecil. Itu lebih mudah dirawat daripada satu tipe raksasa.
infer: mengekstrak bagian dari tipe laininfer dipakai di dalam conditional type untuk menangkap bagian dari tipe yang sedang dicocokkan.
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;type ItemType<T> = T extends (infer U)[] ? U : never;
type A = ItemType<string[]>; // stringinfer bergunainferinfer dalam satu tipe membuat pembaca kehilangan jejak,never sering muncul diam-diam kalau pattern tidak cocok.Type guards dipakai saat runtime untuk memberi petunjuk ke TypeScript bahwa suatu nilai sudah dipersempit.
typeoffunction format(value: string | number) {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value.toFixed(2);
}intype User = { id: string; name: string };
type Admin = { id: string; role: 'admin' };
function isAdmin(value: User | Admin
unknownfunction isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}Type guard sangat penting saat data datang dari luar sistem: request body, local storage, API response, atau parser.
as) bukan pengganti pembuktian runtime.Bagian paling berguna dari advanced types adalah reuse yang muncul dari komposisi, bukan dari copy-paste tipe.
infer.Contoh alur:
type User = {
id: string;
name: string;
email: string;
};
type UserPatch = Partial<User>;
type ApiResult<T>
Di sini:
User adalah sumber kebenaran,Partial<User> memetakan kebutuhan update,ApiResult<T> dipakai ulang untuk banyak domain,Kalau satu tipe menggabungkan union, intersection, conditional, dan infer dalam satu baris panjang, pembaca biasanya berhenti memahami logikanya.
Solusi:
Kadang tipe jadi terlalu canggih hanya untuk menutupi model domain yang sebenarnya belum jelas.
Kalau struktur datanya masih berubah-ubah, jangan buru-buru optimalkan tipe. Rapikan model domain dulu.
never muncul tanpa sengajanever sering berarti:
Kalau ini terjadi, cek asumsi input type dan pattern matching-nya.
as SomeType memang cepat, tapi kalau dipakai terus, kamu hanya memindahkan masalah ke runtime.
TypeScript hanya membantu di compile time. Kalau data dari API salah, atau guard kamu lemah, runtime tetap bisa rusak.
Jadi, advanced types harus dipasangkan dengan:
type ApiSuccess<T> = { status: 'success'; data: T };
type ApiError = { status: 'error'; message: string };
type
Pola ini menunjukkan hubungan yang rapi antara:
infer untuk ekstraksi,A | B, satu dari beberapa kemungkinan.A & B, gabungan semua kontrak.keyof dan [K in ...].infer: mekanisme ekstraksi tipe di dalam conditional type.Types, Interfaces, Generics, Narrowing, dan Utility Types di TypeScript