Buka kekuatan TypeScript dengan tipe kondisional dan terpetakan. Pelajari cara membuat aplikasi fleksibel dan aman-tipe yang beradaptasi dengan data kompleks. Kuasai kode TypeScript dinamis.
Pola TypeScript Tingkat Lanjut: Penguasaan Tipe Kondisional dan Terpetakan
Kekuatan TypeScript terletak pada kemampuannya untuk menyediakan pengetikan yang kuat, memungkinkan Anda untuk menangkap kesalahan lebih awal dan menulis kode yang lebih mudah dipelihara. Meskipun tipe dasar seperti string
, number
, dan boolean
adalah fundamental, fitur-fitur canggih TypeScript seperti tipe kondisional dan terpetakan membuka dimensi baru fleksibilitas dan keamanan tipe. Panduan komprehensif ini akan mendalami konsep-konsep kuat ini, membekali Anda dengan pengetahuan untuk membuat aplikasi TypeScript yang benar-benar dinamis dan dapat beradaptasi.
Apa itu Tipe Kondisional?
Tipe kondisional memungkinkan Anda mendefinisikan tipe yang bergantung pada suatu kondisi, mirip dengan operator ternary di JavaScript (condition ? trueValue : falseValue
). Tipe ini memungkinkan Anda untuk mengekspresikan hubungan tipe yang kompleks berdasarkan apakah suatu tipe memenuhi batasan tertentu.
Sintaks
Sintaks dasar untuk tipe kondisional adalah:
T extends U ? X : Y
T
: Tipe yang sedang diperiksa.U
: Tipe pembanding untuk pemeriksaan.extends
: Kata kunci yang menunjukkan hubungan subtipe.X
: Tipe yang akan digunakan jikaT
dapat ditetapkan keU
.Y
: Tipe yang akan digunakan jikaT
tidak dapat ditetapkan keU
.
Pada intinya, jika T extends U
bernilai benar, tipe akan menjadi X
; jika tidak, akan menjadi Y
.
Contoh Praktis
1. Menentukan Tipe dari Parameter Fungsi
Katakanlah Anda ingin membuat tipe yang menentukan apakah parameter fungsi adalah string atau angka:
type ParamType<T> = T extends string ? string : number;
function processValue(value: ParamType<string | number>): void {
if (typeof value === "string") {
console.log("Value is a string:", value);
} else {
console.log("Value is a number:", value);
}
}
processValue("hello"); // Hasil: Value is a string: hello
processValue(123); // Hasil: Value is a number: 123
Dalam contoh ini, ParamType<T>
adalah tipe kondisional. Jika T
adalah string, tipe akan menjadi string
; jika tidak, akan menjadi number
. Fungsi processValue
menerima string atau angka berdasarkan tipe kondisional ini.
2. Mengekstrak Tipe Kembalian Berdasarkan Tipe Input
Bayangkan sebuah skenario di mana Anda memiliki fungsi yang mengembalikan tipe berbeda berdasarkan input. Tipe kondisional dapat membantu Anda mendefinisikan tipe kembalian yang benar:
interface StringProcessor {
process(input: string): number;
}
interface NumberProcessor {
process(input: number): string;
}
type Processor<T> = T extends string ? StringProcessor : NumberProcessor;
function createProcessor<T extends string | number>(input: T): Processor<T> {
if (typeof input === "string") {
return { process: (input: string) => input.length } as Processor<T>;
} else {
return { process: (input: number) => input.toString() } as Processor<T>;
}
}
const stringProcessor = createProcessor("example");
const numberProcessor = createProcessor(42);
console.log(stringProcessor.process("example")); // Hasil: 7
console.log(numberProcessor.process(42)); // Hasil: "42"
Di sini, tipe Processor<T>
secara kondisional memilih antara StringProcessor
atau NumberProcessor
berdasarkan tipe inputnya. Ini memastikan bahwa fungsi createProcessor
mengembalikan tipe objek prosesor yang benar.
3. Discriminated Unions
Tipe kondisional sangat kuat saat bekerja dengan discriminated unions. Discriminated union adalah tipe union di mana setiap anggota memiliki properti tipe singleton yang sama (diskriminan). Ini memungkinkan Anda untuk mempersempit tipe berdasarkan nilai dari properti tersebut.
interface Square {
kind: "square";
size: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Circle;
type Area<T extends Shape> = T extends { kind: "square" } ? number : string;
function calculateArea(shape: Shape): Area<typeof shape> {
if (shape.kind === "square") {
return shape.size * shape.size;
} else {
return Math.PI * shape.radius * shape.radius;
}
}
const mySquare: Square = { kind: "square", size: 5 };
const myCircle: Circle = { kind: "circle", radius: 3 };
console.log(calculateArea(mySquare)); // Hasil: 25
console.log(calculateArea(myCircle)); // Hasil: 28.274333882308138
Dalam contoh ini, tipe Shape
adalah discriminated union. Tipe Area<T>
menggunakan tipe kondisional untuk menentukan apakah bentuknya persegi atau lingkaran, mengembalikan number
untuk persegi dan string
untuk lingkaran (meskipun dalam skenario dunia nyata, Anda kemungkinan besar menginginkan tipe kembalian yang konsisten, ini menunjukkan prinsipnya).
Poin Penting tentang Tipe Kondisional
- Memungkinkan pendefinisian tipe berdasarkan kondisi.
- Meningkatkan keamanan tipe dengan mengekspresikan hubungan tipe yang kompleks.
- Berguna untuk bekerja dengan parameter fungsi, tipe kembalian, dan discriminated unions.
Apa itu Tipe Terpetakan?
Tipe terpetakan menyediakan cara untuk mengubah tipe yang ada dengan memetakan propertinya. Tipe ini memungkinkan Anda membuat tipe baru berdasarkan properti dari tipe lain, menerapkan modifikasi seperti membuat properti menjadi opsional, readonly, atau mengubah tipenya.
Sintaks
Sintaks umum untuk tipe terpetakan adalah:
type NewType<T> = {
[K in keyof T]: ModifiedType;
};
T
: Tipe input.keyof T
: Operator tipe yang mengembalikan gabungan (union) dari semua kunci properti diT
.K in keyof T
: Melakukan iterasi pada setiap kunci dikeyof T
, menugaskan setiap kunci ke variabel tipeK
.ModifiedType
: Tipe tujuan pemetaan setiap properti. Ini dapat mencakup tipe kondisional atau transformasi tipe lainnya.
Contoh Praktis
1. Membuat Properti Menjadi Opsional
Anda dapat menggunakan tipe terpetakan untuk membuat semua properti dari tipe yang ada menjadi opsional:
interface User {
id: number;
name: string;
email: string;
}
type PartialUser = {
[K in keyof User]?: User[K];
};
const partialUser: PartialUser = {
name: "John Doe",
}; // Valid, karena 'id' dan 'email' bersifat opsional
Di sini, PartialUser
adalah tipe terpetakan yang melakukan iterasi pada kunci dari antarmuka User
. Untuk setiap kunci K
, ia membuat properti menjadi opsional dengan menambahkan pengubah ?
. User[K]
mengambil tipe dari properti K
dari antarmuka User
.
2. Membuat Properti Menjadi Readonly
Demikian pula, Anda dapat membuat semua properti dari tipe yang ada menjadi readonly:
interface Product {
id: number;
name: string;
price: number;
}
type ReadonlyProduct = {
readonly [K in keyof Product]: Product[K];
};
const readonlyProduct: ReadonlyProduct = {
id: 123,
name: "Example Product",
price: 25.00,
};
// readonlyProduct.price = 30.00; // Error: Tidak dapat menetapkan nilai ke 'price' karena ini adalah properti read-only.
Dalam kasus ini, ReadonlyProduct
adalah tipe terpetakan yang menambahkan pengubah readonly
ke setiap properti dari antarmuka Product
.
3. Mengubah Tipe Properti
Tipe terpetakan juga dapat digunakan untuk mengubah tipe properti. Sebagai contoh, Anda dapat membuat tipe yang mengubah semua properti string menjadi angka:
interface Config {
apiUrl: string;
timeout: string;
maxRetries: number;
}
type NumericConfig = {
[K in keyof Config]: Config[K] extends string ? number : Config[K];
};
const numericConfig: NumericConfig = {
apiUrl: 123, // Harus berupa angka karena pemetaan
timeout: 456, // Harus berupa angka karena pemetaan
maxRetries: 3,
};
Contoh ini menunjukkan penggunaan tipe kondisional di dalam tipe terpetakan. Untuk setiap properti K
, ia memeriksa apakah tipe dari Config[K]
adalah string. Jika ya, tipenya dipetakan ke number
; jika tidak, tipenya tetap tidak berubah.
4. Pemetaan Ulang Kunci (sejak TypeScript 4.1)
TypeScript 4.1 memperkenalkan kemampuan untuk memetakan ulang kunci dalam tipe terpetakan menggunakan kata kunci as
. Ini memungkinkan Anda untuk membuat tipe baru dengan nama properti yang berbeda berdasarkan tipe asli.
interface Event {
eventId: string;
eventName: string;
eventDate: Date;
}
type TransformedEvent = {
[K in keyof Event as `new${Capitalize<string&K>}`]: Event[K];
};
// Hasil:
// {
// newEventId: string;
// newEventName: string;
// newEventDate: Date;
// }
//Fungsi Capitalize digunakan untuk membuat huruf pertama menjadi kapital
type Capitalize<S extends string> = Uppercase<string&S> extends never ? string : `$Capitalize<S>`;
//Penggunaan dengan objek nyata
const myEvent: TransformedEvent = {
newEventId: "123",
newEventName: "New Name",
newEventDate: new Date()
};
Di sini, tipe TransformedEvent
memetakan ulang setiap kunci K
ke kunci baru dengan awalan "new" dan dikapitalisasi. Fungsi utilitas `Capitalize` memastikan huruf pertama dari kunci menjadi kapital. Interseksi `string & K` memastikan kita hanya berurusan dengan kunci string dan mendapatkan tipe literal yang benar dari K.
Pemetaan ulang kunci membuka kemungkinan yang kuat untuk mengubah dan mengadaptasi tipe sesuai kebutuhan spesifik. Ini memungkinkan Anda untuk mengganti nama, memfilter, atau memodifikasi kunci berdasarkan logika yang kompleks.
Poin Penting tentang Tipe Terpetakan
- Memungkinkan transformasi tipe yang ada dengan memetakan propertinya.
- Memungkinkan pembuatan properti menjadi opsional, readonly, atau mengubah tipenya.
- Berguna untuk membuat tipe baru berdasarkan properti dari tipe lain.
- Pemetaan ulang kunci (diperkenalkan di TypeScript 4.1) menawarkan fleksibilitas yang lebih besar dalam transformasi tipe.
Menggabungkan Tipe Kondisional dan Terpetakan
Kekuatan sebenarnya dari tipe kondisional dan terpetakan muncul saat Anda menggabungkannya. Ini memungkinkan Anda untuk membuat definisi tipe yang sangat fleksibel dan ekspresif yang dapat beradaptasi dengan berbagai skenario.Contoh: Memfilter Properti Berdasarkan Tipe
Katakanlah Anda ingin membuat tipe yang memfilter properti sebuah objek berdasarkan tipenya. Sebagai contoh, Anda mungkin ingin mengekstrak hanya properti string dari sebuah objek.
interface Data {
name: string;
age: number;
city: string;
country: string;
isEmployed: boolean;
}
type StringProperties<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
type StringData = StringProperties<Data>;
// Hasil:
// {
// name: string;
// city: string;
// country: string;
// }
const stringData: StringData = {
name: "John",
city: "New York",
country: "USA",
};
Dalam contoh ini, tipe StringProperties<T>
menggunakan tipe terpetakan dengan pemetaan ulang kunci dan tipe kondisional. Untuk setiap properti K
, ia memeriksa apakah tipe dari T[K]
adalah string. Jika ya, kuncinya dipertahankan; jika tidak, kunci dipetakan ke never
, yang secara efektif menyaringnya. never
sebagai kunci tipe terpetakan akan menghapusnya dari tipe yang dihasilkan. Ini memastikan bahwa hanya properti string yang disertakan dalam tipe StringData
.
Tipe Utilitas di TypeScript
TypeScript menyediakan beberapa tipe utilitas bawaan yang memanfaatkan tipe kondisional dan terpetakan untuk melakukan transformasi tipe umum. Memahami tipe utilitas ini dapat secara signifikan menyederhanakan kode Anda dan meningkatkan keamanan tipe.
Tipe Utilitas Umum
Partial<T>
: Membuat semua propertiT
menjadi opsional.Readonly<T>
: Membuat semua propertiT
menjadi readonly.Required<T>
: Membuat semua propertiT
menjadi wajib. (menghilangkan pengubah?
)Pick<T, K extends keyof T>
: Memilih satu set propertiK
dariT
.Omit<T, K extends keyof T>
: Menghapus satu set propertiK
dariT
.Record<K extends keyof any, T>
: Membangun tipe dengan satu set propertiK
dari tipeT
.Exclude<T, U>
: Mengecualikan semua tipe dariT
yang dapat ditetapkan keU
.Extract<T, U>
: Mengekstrak semua tipe dariT
yang dapat ditetapkan keU
.NonNullable<T>
: Mengecualikannull
danundefined
dariT
.Parameters<T>
: Mendapatkan parameter dari tipe fungsiT
dalam sebuah tuple.ReturnType<T>
: Mendapatkan tipe kembalian dari tipe fungsiT
.InstanceType<T>
: Mendapatkan tipe instans dari tipe fungsi konstruktorT
.ThisType<T>
: Berfungsi sebagai penanda untuk tipethis
kontekstual.
Tipe utilitas ini dibangun menggunakan tipe kondisional dan terpetakan, yang menunjukkan kekuatan dan fleksibilitas fitur-fitur canggih TypeScript ini. Sebagai contoh, Partial<T>
didefinisikan sebagai:
type Partial<T> = {
[P in keyof T]?: T[P];
};
Praktik Terbaik Menggunakan Tipe Kondisional dan Terpetakan
Meskipun tipe kondisional dan terpetakan sangat kuat, mereka juga dapat membuat kode Anda lebih kompleks jika tidak digunakan dengan hati-hati. Berikut adalah beberapa praktik terbaik yang perlu diingat:
- Jaga Tetap Sederhana: Hindari tipe kondisional dan terpetakan yang terlalu kompleks. Jika definisi tipe menjadi terlalu rumit, pertimbangkan untuk memecahnya menjadi bagian-bagian yang lebih kecil dan lebih mudah dikelola.
- Gunakan Nama yang Bermakna: Berikan nama deskriptif pada tipe kondisional dan terpetakan Anda yang dengan jelas menunjukkan tujuannya.
- Dokumentasikan Tipe Anda: Tambahkan komentar untuk menjelaskan logika di balik tipe kondisional dan terpetakan Anda, terutama jika kompleks.
- Manfaatkan Tipe Utilitas: Sebelum membuat tipe kondisional atau terpetakan kustom, periksa apakah tipe utilitas bawaan dapat mencapai hasil yang sama.
- Uji Tipe Anda: Pastikan tipe kondisional dan terpetakan Anda berfungsi seperti yang diharapkan dengan menulis pengujian unit yang mencakup berbagai skenario.
- Pertimbangkan Performa: Komputasi tipe yang kompleks dapat memengaruhi waktu kompilasi. Waspadai implikasi performa dari definisi tipe Anda.
Kesimpulan
Tipe kondisional dan terpetakan adalah alat penting untuk menguasai TypeScript. Keduanya memungkinkan Anda membuat aplikasi yang sangat fleksibel, aman-tipe, dan mudah dipelihara yang beradaptasi dengan struktur data kompleks dan persyaratan dinamis. Dengan memahami dan menerapkan konsep yang dibahas dalam panduan ini, Anda dapat membuka potensi penuh TypeScript dan menulis kode yang lebih kuat dan terukur. Saat Anda terus menjelajahi TypeScript, ingatlah untuk bereksperimen dengan berbagai kombinasi tipe kondisional dan terpetakan untuk menemukan cara-cara baru dalam memecahkan masalah pengetikan yang menantang. Kemungkinannya benar-benar tidak terbatas.