Typescript Types Interfaces Generics Narrowing Utility Types
Selesai membaca dokumentasi ini?
Kembali ke rujukan utama TypeScript untuk melanjutkan topik lainnya.
Kembali ke rujukan utama TypeScript untuk melanjutkan topik lainnya.
TypeScript menambahkan sistem tipe di atas JavaScript supaya struktur data, kontrak fungsi, dan alur kontrol lebih aman saat dikembangkan. Lima konsep yang paling sering dipakai sehari-hari adalah:
type vs interfaceKeduanya sering tumpang tindih, terutama untuk bentuk object. Pilih berdasarkan kebutuhan, bukan kebiasaan.
interface kalauContoh:
interface User {
id: string;
name: string;
}
interface Admin extends User {
role: 'admin';
}type kalauContoh:
type Status = 'idle' | 'loading' | 'success' | 'error';
type Id = string | number;
type Point = [number, number];
type Handler = (value:interface biasanya lebih enak dibaca.type biasanya lebih kuat.interface mendukung extends langsung dan declaration merging.type lebih cocok untuk union/intersection dan transformasi tipe.type dan interface sama-sama bisa dipakai untuk object shape, tapi type tidak bisa dideclaration-merge seperti interface.Generics dipakai saat kamu ingin menulis satu pola yang tetap aman untuk banyak jenis data.
Tanpa generics, kode reusable sering jatuh ke any atau union yang terlalu lebar. Dengan generics, TypeScript tetap bisa menjaga hubungan antara input dan output.
function identity<T>(value: T): T {
return value;
}
const a = identity('hello'); // string
const b = identity(123); // numberT di sini adalah parameter tipe. TypeScript mengisi nilainya dari argumen yang dipakai.
function first<T>(items: T[]): T | undefined {
return items[0];
}
const user = first([{ id: 1 }, { id: 2 }]);Kelebihannya bukan cuma menghindari duplikasi. Yang lebih penting: tipe hasil tetap nyambung dengan tipe input.
extendsKadang generic harus dibatasi supaya properti tertentu pasti tersedia.
function getLength<T extends { length: number }>(value: T): number {
return value.length;
}
getLength('abc');
getLength([1, 2, Ini berguna saat fungsi butuh bentuk minimal, tapi tetap ingin fleksibel.
type ApiResponse<T = unknown> = {
data: T;
error?: string;
};
type UserResponse = ApiResponse<{ id: string; name: string }>;Pola ini berguna untuk response API, wrapper state, dan komponen yang punya data shape berbeda-beda.
any atau type assertionRule of thumb: pakai generics saat tipe output bergantung pada input atau saat kamu ingin menulis abstraksi yang memang dipakai berulang.
Narrowing adalah cara TypeScript mempersempit tipe dari bentuk yang lebih luas menjadi bentuk yang lebih spesifik berdasarkan pengecekan runtime.
typeoffunction format(value: string | number) {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value.toFixed(2);
}intype A = { kind: 'a'; foo: string };
type B = { kind: 'b'; bar: number };
function handle(input: A | B
function parse(value: 'yes' | 'no' | boolean) {
if (value === true) {
return 'truthy';
}
return value;
}type Loading = { status: 'loading' };
type Success = { status: 'success'; data: string };
type ErrorState = { status: 'error'; message
Discriminated union adalah pola paling aman dan paling enak dirawat untuk state yang punya beberapa mode.
function f(value: string | null) {
if (value) {
// string, tapi empty string ikut dianggap false
return value;
}
}Truthy check memang sering berguna, tapi untuk data yang valid bisa bernilai '', 0, atau false, check ini bisa salah arah. Lebih aman pakai pengecekan eksplisit kalau nilai kosong masih valid.
Kalau variabel berubah setelah dicek, asumsi tipe bisa jadi tidak valid lagi.
let value: string | number = 'abc';
if (typeof value === 'string') {
value = 123;
// di sini value sudah bukan string lagi
}TypeScript hanya memahami alur yang bisa dianalisis statis. Setelah data lewat fungsi lain, disimpan ke struktur yang lebih longgar, atau diubah jadi unknown/any, informasi tipe bisa hilang.
const el = document.getElementById('app') as HTMLDivElement;Assertion bukan narrowing. Ini bilang ke compiler “percaya saja”, bukan membuktikan tipe. Kalau salah, bug pindah ke runtime.
Array.filter(Boolean) sering dianggap amanPola ini sering membuang nilai falsy yang sebenarnya valid, dan hasil tipenya juga tidak selalu sespesifik yang diharapkan. Kalau butuh filter tipe, pakai type guard eksplisit.
function isString(value: unknown): value is string {
return typeof value === 'string';
}
const values: unknown[] = ['a', 1, 'b'
Type guard dipakai saat logika narrowing berulang dan ingin dibungkus dalam fungsi.
function isSuccess(state: State): state is Success {
return state.status === 'success';
}Ini membantu saat pengecekan dipakai di beberapa tempat tanpa mengulang logika switch.
Utility types adalah tipe bawaan TypeScript yang sangat membantu saat kamu ingin memodifikasi bentuk tipe tanpa menulis ulang dari nol.
Partial<T>Membuat semua properti jadi opsional.
type User = {
id: string;
name: string;
email: string;
};
type UserPatch = Partial<User>;Cocok untuk update form atau patch payload.
Required<T>Membuat semua properti wajib.
Pick<T, K>Mengambil properti tertentu saja.
type UserSummary = Pick<User, 'id' | 'name'>;Omit<T, K>Membuang properti tertentu.
type PublicUser = Omit<User, 'email'>;Record<K, T>Membuat object dengan key tertentu dan value seragam.
type Role = 'admin' | 'editor' | 'viewer';
type PermissionMap = Record<Role, boolean>;Readonly<T>Membuat properti tidak bisa diubah.
ReturnType<T> dan Parameters<T>Berguna saat ingin mengambil tipe dari fungsi yang sudah ada.
function buildUser(name: string) {
return { id: '1', name };
}
type BuiltUser = ReturnType<typeof buildUser>;
type BuildUserArgs = Parameters<typeof buildUser>;Partial<T> terlalu sering dipakai untuk semua kasus update, padahal bisa menyembunyikan field wajibOmit<T, K> tidak selalu aman kalau field yang dihapus sebenarnya masih dipakai di layer laininterface User {
id: string;
name: string;
email: string;
role: 'admin' | 'member';
}
type UpdateUserPayload = Partial<Pick<Usertype ApiResult<T> = {
data: T | null;
error: string | null;
};
type UserListResult = ApiResult<User[]>;type LoadState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status:Pola seperti ini biasanya lebih aman daripada satu object besar dengan banyak properti opsional.
any saat sebenarnya cukup unknown + narrowingPartial di semua tempat updateinterface selalu lebih baik dari type, atau sebaliknya