Jelajahi teknik TypeScript tingkat lanjut dengan template literal untuk manipulasi tipe string. Pelajari cara mem-parsing, mengubah, dan memvalidasi tipe string secara efektif.
Parsing Template Literal TypeScript: Manipulasi Tipe String Tingkat Lanjut
Sistem tipe TypeScript menyediakan alat yang kuat untuk memanipulasi dan memvalidasi data pada waktu kompilasi. Di antara alat-alat ini, template literal menawarkan pendekatan unik untuk manipulasi tipe string. Artikel ini membahas aspek-aspek lanjutan dari parsing template literal, menunjukkan cara membuat logika tingkat tipe yang canggih untuk data berbasis string.
Apa itu Tipe Template Literal?
Tipe template literal, diperkenalkan di TypeScript 4.1, memungkinkan Anda mendefinisikan tipe string berdasarkan literal string dan tipe lainnya. Mereka menggunakan backtick (`) untuk mendefinisikan tipe, mirip dengan template literal di JavaScript.
Sebagai contoh:
type Color = "red" | "green" | "blue";
type Shade = "light" | "dark";
type ColorCombination = `${Shade} ${Color}`;
// ColorCombination sekarang adalah "light red" | "light green" | "light blue" | "dark red" | "dark green" | "dark blue"
Fitur yang tampaknya sederhana ini membuka berbagai kemungkinan untuk pemrosesan string pada waktu kompilasi.
Penggunaan Dasar Tipe Template Literal
Sebelum masuk ke teknik tingkat lanjut, mari kita ulas beberapa kasus penggunaan fundamental.
Menggabungkan Literal String
Anda dapat dengan mudah menggabungkan literal string dan tipe lain untuk membuat tipe string baru:
type Greeting = `Hello, ${string}!`;
// Contoh Penggunaan
const greet = (name: string): Greeting => `Hello, ${name}!`;
const message: Greeting = greet("World"); // Valid
const invalidMessage: Greeting = "Goodbye, World!"; // Error: Tipe '"Goodbye, World!"' tidak dapat ditetapkan ke tipe '`Hello, ${string}!`'.
Menggunakan Tipe Union
Tipe union memungkinkan Anda mendefinisikan sebuah tipe sebagai kombinasi dari beberapa nilai yang mungkin. Template literal dapat memasukkan tipe union untuk menghasilkan union tipe string yang lebih kompleks:
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = `/api/users` | `/api/products`;
type Route = `${HTTPMethod} ${Endpoint}`;
// Route sekarang adalah "GET /api/users" | "POST /api/users" | "PUT /api/users" | "DELETE /api/users" | "GET /api/products" | "POST /api/products" | "PUT /api/products" | "DELETE /api/products"
Teknik Parsing Template Literal Tingkat Lanjut
Kekuatan sebenarnya dari tipe template literal terletak pada kemampuannya untuk digabungkan dengan fitur TypeScript tingkat lanjut lainnya, seperti tipe kondisional dan inferensi tipe, untuk mem-parsing dan memanipulasi tipe string.
Menginferensi Bagian dari Tipe String
Anda dapat menggunakan kata kunci infer di dalam tipe kondisional untuk mengekstrak bagian spesifik dari sebuah tipe string. Ini adalah dasar untuk mem-parsing tipe string.
Pertimbangkan sebuah tipe yang mengekstrak ekstensi file dari nama file:
type GetFileExtension = T extends `${string}.${infer Extension}` ? Extension : never;
// Contoh
type Extension1 = GetFileExtension<"myFile.txt">; // "txt"
type Extension2 = GetFileExtension<"anotherFile.image.jpg">; // "image.jpg" (mengambil ekstensi terakhir)
type Extension3 = GetFileExtension<"noExtension">; // never
Dalam contoh ini, tipe kondisional memeriksa apakah tipe input T cocok dengan pola ${string}.${infer Extension}. Jika cocok, ia menginferensikan bagian setelah titik terakhir ke dalam variabel tipe Extension, yang kemudian dikembalikan. Jika tidak, ia mengembalikan never.
Parsing dengan Inferensi Ganda
Anda dapat menggunakan beberapa kata kunciinfer dalam template literal yang sama untuk mengekstrak beberapa bagian dari tipe string secara bersamaan.
type ParseConnectionString =
T extends `${infer Protocol}://${infer Host}:${infer Port}` ?
{ protocol: Protocol, host: Host, port: Port } : never;
// Contoh
type Connection = ParseConnectionString<"http://localhost:3000">;
// { protocol: "http", host: "localhost", port: "3000" }
type InvalidConnection = ParseConnectionString<"invalid-connection">; // never
Tipe ini mem-parsing string koneksi menjadi komponen protokol, host, dan port-nya.
Definisi Tipe Rekursif untuk Parsing Kompleks
Untuk struktur string yang lebih kompleks, Anda dapat menggunakan definisi tipe rekursif. Ini memungkinkan Anda untuk mem-parsing bagian-bagian dari tipe string secara berulang hingga mencapai hasil yang diinginkan.
Katakanlah Anda ingin memecah sebuah string menjadi array karakter individu di tingkat tipe. Ini jauh lebih canggih.
type StringToArray =
T extends `${infer Char}${infer Rest}`
? StringToArray
: Acc;
// Contoh
type MyArray = StringToArray<"hello">; // ["h", "e", "l", "l", "o"]
Penjelasan:
StringToArray<T extends string, Acc extends string[] = []>: Ini mendefinisikan tipe generik bernamaStringToArrayyang menerima tipe stringTsebagai input dan akumulator opsionalAccyang defaultnya adalah array string kosong. Akumulator akan menyimpan karakter saat kita memprosesnya.T extends `${infer Char}${infer Rest}`: Ini adalah pemeriksaan tipe kondisional. Ia memeriksa apakah string inputTdapat dipecah menjadi karakter pertamaChardan sisa stringRest. Kata kunciinferdigunakan untuk menangkap bagian-bagian ini.StringToArray<Rest, [...Acc, Char]>: Jika pemecahan berhasil, kita secara rekursif memanggilStringToArraydengan sisa stringRestdan akumulator baru. Akumulator baru dibuat dengan menyebarkanAccyang ada dan menambahkan karakter saat iniCharke akhir. Ini secara efektif menambahkan karakter ke array akumulasi.Acc: Jika string kosong (tipe kondisional gagal, yang berarti tidak ada lagi karakter), kita mengembalikan array yang terakumulasiAcc.
Contoh ini menunjukkan kekuatan rekursi dalam memanipulasi tipe string. Setiap panggilan rekursif mengupas satu karakter dan menambahkannya ke array hingga string menjadi kosong.
Bekerja dengan Delimiter
Template literal dapat dengan mudah digunakan dengan delimiter untuk mem-parsing string. Katakanlah Anda ingin mengekstrak kata-kata yang dipisahkan oleh koma.
type SplitString =
T extends `${infer First}${D}${infer Rest}`
? [First, ...SplitString]
: [T];
// Contoh
type Words = SplitString<"apple,banana,cherry", ",">; // ["apple", "banana", "cherry"]
Tipe ini secara rekursif memecah string pada setiap kemunculan delimiter D.
Aplikasi Praktis
Teknik parsing template literal tingkat lanjut ini memiliki banyak aplikasi praktis dalam proyek TypeScript.
Validasi Data
Anda dapat memvalidasi data berbasis string terhadap pola spesifik pada waktu kompilasi. Misalnya, memvalidasi alamat email, nomor telepon, atau nomor kartu kredit. Pendekatan ini memberikan umpan balik awal dan mengurangi kesalahan saat runtime.
Berikut adalah contoh validasi format alamat email yang disederhanakan:
type EmailFormat = `${string}@${string}.${string}`;
const validateEmail = (email: string): email is EmailFormat => {
// Kenyataannya, regex yang jauh lebih kompleks akan digunakan untuk validasi email yang benar.
// Ini hanya untuk tujuan demonstrasi.
return /.+@.+\..+/.test(email);
}
const validEmail: EmailFormat = "user@example.com"; // Valid
const invalidEmail: EmailFormat = "invalid-email"; // Tipe 'string' tidak dapat ditetapkan ke tipe '`${string}@${string}.${string}`'.
if(validateEmail(validEmail)) {
console.log("Email Valid");
}
if(validateEmail("invalid-email")) {
console.log("Ini tidak akan tercetak.");
}
Meskipun validasi runtime dengan regex masih diperlukan untuk kasus di mana pemeriksa tipe tidak dapat sepenuhnya memberlakukan batasan (misalnya, saat berurusan dengan input eksternal), tipe EmailFormat memberikan baris pertahanan pertama yang berharga pada waktu kompilasi.
Generasi Endpoint API
Template literal dapat digunakan untuk menghasilkan tipe endpoint API berdasarkan URL dasar dan serangkaian parameter. Ini dapat membantu memastikan konsistensi dan keamanan tipe saat bekerja dengan API.
type BaseURL = "https://api.example.com";
type Resource = "users" | "products";
type ID = string | number;
type GetEndpoint = `${BaseURL}/${T}/${U}`;
// Contoh
type UserEndpoint = GetEndpoint<"users", 123>; // "https://api.example.com/users/123"
type ProductEndpoint = GetEndpoint<"products", "abc-456">; // "https://api.example.com/products/abc-456"
Generasi Kode
Dalam skenario yang lebih canggih, tipe template literal dapat digunakan sebagai bagian dari proses generasi kode. Misalnya, menghasilkan kueri SQL berdasarkan skema atau membuat komponen UI berdasarkan file konfigurasi.
Internasionalisasi (i18n)
Template literal bisa sangat berharga dalam skenario i18n. Sebagai contoh, pertimbangkan sebuah sistem di mana kunci terjemahan mengikuti konvensi penamaan tertentu:
type SupportedLanguages = 'en' | 'es' | 'fr';
type TranslationKeyPrefix = 'common' | 'product' | 'checkout';
type TranslationKey = `${TPrefix}.${string}`;
// Contoh penggunaan:
const getTranslation = (key: TranslationKey, lang: SupportedLanguages): string => {
// Mensimulasikan pengambilan terjemahan dari bundel sumber daya berdasarkan kunci dan bahasa
const translations: Record> = {
'common.greeting': {
en: 'Hello',
es: 'Hola',
fr: 'Bonjour',
},
'product.description': {
en: 'A fantastic product!',
es: '¡Un producto fantástico!',
fr: 'Un produit fantastique !',
},
};
const translation = translations[key]?.[lang];
return translation || `Terjemahan tidak ditemukan untuk kunci: ${key} dalam bahasa: ${lang}`;
};
const englishGreeting = getTranslation('common.greeting', 'en'); // Hello
const spanishDescription = getTranslation('product.description', 'es'); // ¡Un producto fantástico!
const unknownTranslation = getTranslation('nonexistent.key' as TranslationKey, 'en'); // Terjemahan tidak ditemukan untuk kunci: nonexistent.key dalam bahasa: en
Tipe TranslationKey memastikan bahwa semua kunci terjemahan mengikuti format yang konsisten, yang menyederhanakan proses pengelolaan terjemahan dan mencegah kesalahan.
Batasan
Meskipun tipe template literal sangat kuat, mereka juga memiliki batasan:
- Kompleksitas: Logika parsing yang kompleks bisa dengan cepat menjadi sulit dibaca dan dipelihara.
- Performa: Penggunaan tipe template literal yang ekstensif dapat memengaruhi performa waktu kompilasi, terutama dalam proyek besar.
- Celah Keamanan Tipe: Seperti yang ditunjukkan dalam contoh validasi email, pemeriksaan waktu kompilasi terkadang tidak cukup. Validasi runtime masih diperlukan untuk kasus di mana data eksternal harus mematuhi format yang ketat.
Praktik Terbaik
Untuk menggunakan tipe template literal secara efektif, ikuti praktik terbaik berikut:
- Buat Tetap Sederhana: Pecah logika parsing yang kompleks menjadi tipe-tipe yang lebih kecil dan mudah dikelola.
- Dokumentasikan Tipe Anda: Dokumentasikan dengan jelas tujuan dan penggunaan tipe template literal Anda.
- Uji Tipe Anda: Buat unit test untuk memastikan tipe Anda berfungsi seperti yang diharapkan.
- Seimbangkan Validasi Waktu Kompilasi dan Runtime: Gunakan tipe template literal untuk validasi dasar dan pemeriksaan runtime untuk skenario yang lebih kompleks.
Kesimpulan
Tipe template literal TypeScript menyediakan cara yang kuat dan fleksibel untuk memanipulasi tipe string pada waktu kompilasi. Dengan menggabungkan template literal dengan tipe kondisional dan inferensi tipe, Anda dapat membuat logika tingkat tipe yang canggih untuk mem-parsing, memvalidasi, dan mengubah data berbasis string. Meskipun ada batasan yang perlu dipertimbangkan, manfaat menggunakan tipe template literal dalam hal keamanan tipe dan kemudahan pemeliharaan kode bisa sangat signifikan.
Dengan menguasai teknik-teknik canggih ini, pengembang dapat membuat aplikasi TypeScript yang lebih kuat dan andal.
Eksplorasi Lebih Lanjut
Untuk memperdalam pemahaman Anda tentang tipe template literal, pertimbangkan untuk menjelajahi topik-topik berikut:
- Mapped Types: Pelajari cara mengubah tipe objek berdasarkan tipe template literal.
- Utility Types: Jelajahi tipe utilitas bawaan TypeScript yang dapat digunakan bersama dengan tipe template literal.
- Advanced Conditional Types: Selami lebih dalam kemampuan tipe kondisional untuk logika tingkat tipe yang lebih kompleks.