Bahasa Indonesia

Panduan komprehensif untuk Mapped Types dan Conditional Types TypeScript, mencakup contoh praktis dan kasus penggunaan tingkat lanjut untuk membangun aplikasi yang kokoh dan type-safe.

Menguasai Mapped Types dan Conditional Types pada TypeScript

TypeScript, superset dari JavaScript, menawarkan fitur-fitur canggih untuk membuat aplikasi yang kokoh dan mudah dipelihara. Di antara fitur-fitur ini, Mapped Types dan Conditional Types menonjol sebagai alat penting untuk manipulasi tipe tingkat lanjut. Panduan ini memberikan gambaran komprehensif tentang konsep-konsep ini, menjelajahi sintaksis, aplikasi praktis, dan kasus penggunaan tingkat lanjut. Baik Anda seorang pengembang TypeScript berpengalaman atau baru memulai perjalanan Anda, artikel ini akan membekali Anda dengan pengetahuan untuk memanfaatkan fitur-fitur ini secara efektif.

Apa itu Mapped Types?

Mapped Types memungkinkan Anda membuat tipe baru dengan mengubah tipe yang sudah ada. Tipe ini melakukan iterasi pada properti dari tipe yang ada dan menerapkan transformasi ke setiap properti. Ini sangat berguna untuk membuat variasi dari tipe yang ada, seperti membuat semua properti menjadi opsional atau hanya-baca (read-only).

Sintaksis Dasar

Sintaksis untuk Mapped Type adalah sebagai berikut:

type NewType<T> = {
  [K in keyof T]: Transformation;
};

Contoh Praktis

Membuat Properti Menjadi Read-Only

Katakanlah Anda memiliki antarmuka (interface) yang merepresentasikan profil pengguna:

interface UserProfile {
  name: string;
  age: number;
  email: string;
}

Anda dapat membuat tipe baru di mana semua properti bersifat hanya-baca (read-only):

type ReadOnlyUserProfile = {
  readonly [K in keyof UserProfile]: UserProfile[K];
};

Sekarang, ReadOnlyUserProfile akan memiliki properti yang sama dengan UserProfile, tetapi semuanya akan bersifat hanya-baca.

Membuat Properti Menjadi Opsional

Demikian pula, Anda dapat membuat semua properti menjadi opsional:

type OptionalUserProfile = {
  [K in keyof UserProfile]?: UserProfile[K];
};

OptionalUserProfile akan memiliki semua properti dari UserProfile, tetapi setiap properti akan menjadi opsional.

Mengubah Tipe Properti

Anda juga dapat mengubah tipe setiap properti. Misalnya, Anda dapat mengubah semua properti menjadi string:

type StringifiedUserProfile = {
  [K in keyof UserProfile]: string;
};

Dalam kasus ini, semua properti di StringifiedUserProfile akan bertipe string.

Apa itu Conditional Types?

Conditional Types memungkinkan Anda mendefinisikan tipe yang bergantung pada suatu kondisi. Tipe ini menyediakan cara untuk mengekspresikan hubungan tipe berdasarkan apakah suatu tipe memenuhi batasan tertentu. Ini mirip dengan operator ternary di JavaScript, tetapi untuk tipe.

Sintaksis Dasar

Sintaksis untuk Conditional Type adalah sebagai berikut:

T extends U ? X : Y

Contoh Praktis

Menentukan Apakah Suatu Tipe adalah String

Mari kita buat tipe yang mengembalikan string jika tipe inputnya adalah string, dan number jika sebaliknya:

type StringOrNumber<T> = T extends string ? string : number;

type Result1 = StringOrNumber<string>;  // string
type Result2 = StringOrNumber<number>;  // number
type Result3 = StringOrNumber<boolean>; // number

Mengekstrak Tipe dari Union

Anda dapat menggunakan conditional types untuk mengekstrak tipe tertentu dari sebuah union type. Contohnya, untuk mengekstrak tipe non-nullable:

type NonNullable<T> = T extends null | undefined ? never : T;

type Result4 = NonNullable<string | null | undefined>; // string

Di sini, jika T adalah null atau undefined, tipenya menjadi never, yang kemudian disaring oleh penyederhanaan union type TypeScript.

Menyimpulkan Tipe (Inferring Types)

Conditional types juga dapat digunakan untuk menyimpulkan tipe menggunakan kata kunci infer. Ini memungkinkan Anda untuk mengekstrak tipe dari struktur tipe yang lebih kompleks.

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

function myFunction(x: number): string {
  return x.toString();
}

type Result5 = ReturnType<typeof myFunction>; // string

Dalam contoh ini, ReturnType mengekstrak tipe kembalian dari sebuah fungsi. Tipe ini memeriksa apakah T adalah fungsi yang menerima argumen apa pun dan mengembalikan tipe R. Jika ya, ia mengembalikan R; jika tidak, ia mengembalikan any.

Menggabungkan Mapped Types dan Conditional Types

Kekuatan sebenarnya dari Mapped Types dan Conditional Types datang dari penggabungan keduanya. Ini memungkinkan Anda untuk membuat transformasi tipe yang sangat fleksibel dan ekspresif.

Contoh: Deep Readonly

Kasus penggunaan yang umum adalah membuat tipe yang membuat semua properti dari sebuah objek, termasuk properti bersarang, menjadi read-only. Hal ini dapat dicapai dengan menggunakan conditional type rekursif.

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

interface Company {
  name: string;
  address: {
    street: string;
    city: string;
  };
}

type ReadonlyCompany = DeepReadonly<Company>;

Di sini, DeepReadonly secara rekursif menerapkan pengubah readonly ke semua properti dan properti bersarangnya. Jika sebuah properti adalah objek, ia secara rekursif memanggil DeepReadonly pada objek tersebut. Jika tidak, ia hanya menerapkan pengubah readonly pada properti tersebut.

Contoh: Memfilter Properti Berdasarkan Tipe

Katakanlah Anda ingin membuat tipe yang hanya menyertakan properti dari tipe tertentu. Anda dapat menggabungkan Mapped Types dan Conditional Types untuk mencapai ini.

type FilterByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};

interface Person {
  name: string;
  age: number;
  isEmployed: boolean;
}

type StringProperties = FilterByType<Person, string>; // { name: string; }

type NonStringProperties = Omit<Person, keyof StringProperties>

Dalam contoh ini, FilterByType melakukan iterasi pada properti T dan memeriksa apakah tipe setiap properti memperluas U. Jika ya, ia menyertakan properti tersebut dalam tipe yang dihasilkan; jika tidak, ia mengecualikannya dengan memetakan kuncinya ke never. Perhatikan penggunaan "as" untuk memetakan ulang kunci. Kami kemudian menggunakan `Omit` dan `keyof StringProperties` untuk menghapus properti string dari antarmuka asli.

Kasus Penggunaan dan Pola Tingkat Lanjut

Di luar contoh-contoh dasar, Mapped Types dan Conditional Types dapat digunakan dalam skenario yang lebih canggih untuk membuat aplikasi yang sangat dapat disesuaikan dan type-safe.

Distributive Conditional Types

Conditional types bersifat distributif ketika tipe yang diperiksa adalah union type. Ini berarti kondisi diterapkan pada setiap anggota union secara individual, dan hasilnya kemudian digabungkan menjadi union type yang baru.

type ToArray<T> = T extends any ? T[] : never;

type Result6 = ToArray<string | number>; // string[] | number[]

Dalam contoh ini, ToArray diterapkan pada setiap anggota dari union string | number secara individual, menghasilkan string[] | number[]. Jika kondisinya tidak distributif, hasilnya akan menjadi (string | number)[].

Menggunakan Utility Types

TypeScript menyediakan beberapa utility types bawaan yang memanfaatkan Mapped Types dan Conditional Types. Utility types ini dapat digunakan sebagai blok bangunan untuk transformasi tipe yang lebih kompleks.

Utility types ini adalah alat canggih yang dapat menyederhanakan manipulasi tipe yang kompleks. Misalnya, Anda dapat menggabungkan Pick dan Partial untuk membuat tipe yang hanya membuat properti tertentu menjadi opsional:

type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
}

type OptionalDescriptionProduct = Optional<Product, "description">;

Dalam contoh ini, OptionalDescriptionProduct memiliki semua properti dari Product, tetapi properti description bersifat opsional.

Menggunakan Template Literal Types

Template Literal Types memungkinkan Anda membuat tipe berdasarkan string literal. Tipe ini dapat digunakan dalam kombinasi dengan Mapped Types dan Conditional Types untuk membuat transformasi tipe yang dinamis dan ekspresif. Misalnya, Anda dapat membuat tipe yang memberi awalan pada semua nama properti dengan string tertentu:

type Prefix<T, P extends string> = {
  [K in keyof T as `${P}${string & K}`]: T[K];
};

interface Settings {
  apiUrl: string;
  timeout: number;
}

type PrefixedSettings = Prefix<Settings, "data_">;

Dalam contoh ini, PrefixedSettings akan memiliki properti data_apiUrl dan data_timeout.

Praktik Terbaik dan Pertimbangan

Kesimpulan

Mapped Types dan Conditional Types adalah fitur canggih di TypeScript yang memungkinkan Anda membuat transformasi tipe yang sangat fleksibel dan ekspresif. Dengan menguasai konsep-konsep ini, Anda dapat meningkatkan keamanan tipe, kemudahan pemeliharaan, dan kualitas keseluruhan aplikasi TypeScript Anda. Dari transformasi sederhana seperti membuat properti menjadi opsional atau hanya-baca hingga transformasi rekursif yang kompleks dan logika kondisional, fitur-fitur ini menyediakan alat yang Anda butuhkan untuk membangun aplikasi yang kokoh dan dapat diskalakan. Teruslah menjelajahi dan bereksperimen dengan fitur-fitur ini untuk membuka potensi penuhnya dan menjadi pengembang TypeScript yang lebih mahir.

Saat Anda melanjutkan perjalanan TypeScript Anda, ingatlah untuk memanfaatkan kekayaan sumber daya yang tersedia, termasuk dokumentasi resmi TypeScript, komunitas online, dan proyek sumber terbuka. Rangkullah kekuatan Mapped Types dan Conditional Types, dan Anda akan siap untuk mengatasi masalah terkait tipe yang paling menantang sekalipun.