Jangan Cuma Pakai `any`: Tiga Trik Type-Safety TypeScript yang Bikin Code Kamu Tahan Banting
Hai gaes, kalau kamu sudah terjun ke dunia development—entah itu bikin website keren di front-end atau mengurus logika server di back-end—pasti tahu bahwa menjaga kualitas kode itu sama pentingnya dengan ngopi saat deadline. Kita tidak cuma butuh kode yang jalan, tapi kode yang tahan banting, mudah dibaca, dan tidak bikin kita nangis saat harus maintenance enam bulan kemudian.
Nah, kalau kamu sudah pindah dari JavaScript murni ke TypeScript, itu adalah langkah maju yang luar biasa. Selamat! Kamu sudah memilih jalan yang benar: jalan menuju Type-Safety. Tapi tunggu dulu, apakah kamu termasuk yang sering tergoda untuk memakai jurus pamungkas yang paling mudah dan paling cepat? Ya, saya sedang bicara tentang any.
Memakai any itu rasanya seperti menyelesaikan masalah dengan menutup mata. Memang cepat, semua error hilang, tapi kita baru saja mengundang semua potensi bug kembali masuk ke dalam rumah yang sudah susah payah kita bersihkan. Di mata Javapixa Creative Studio, kode yang berkualitas adalah fondasi utama. Kami percaya, aplikasi terbaik, yang mampu menangani ratusan ribu traffic per detik, tidak dibangun di atas fondasi any.
Di artikel ini, kita akan bedah tuntas tiga trik type-safety di TypeScript yang wajib kamu kuasai. Ini bukan cuma soal teori, tapi tips yang kami pakai sehari-hari saat membangun aplikasi kompleks dan enterprise untuk klien-klien kami. Siap-siap, karena setelah ini, level coding kamu akan naik kelas!
*
Kenapa any Itu Musuh Dalam Selimut?
Sebelum kita masuk ke triknya, mari kita sepakati dulu: any bukanlah Type-Safety. Ketika kamu menggunakan any, kamu memberitahu TypeScript, "Hai TS, jangan ganggu aku. Aku tahu apa yang aku lakukan, dan biarkan aku memanggil properti apa pun yang aku mau dari objek ini."
Masalahnya, begitu kamu menggunakan any, semua manfaat yang ditawarkan TypeScript (seperti autocompletion, refactoring yang aman, dan penemuan bug saat compile time) langsung hilang. Kode kamu seketika kembali menjadi JavaScript murni yang rentan.
Bayangkan kamu sedang membangun aplikasi manajemen proyek yang dikerjakan oleh tim besar. Jika satu function mengembalikan any, developer lain harus membaca semua implementasi function itu hanya untuk tahu struktur data yang sebenarnya. Ribet, kan? Itu sebabnya, developer elite di Javapixa selalu berusaha keras untuk menghindari any kecuali dalam situasi yang sangat spesifik dan terkendali (misalnya, saat berinteraksi dengan library pihak ketiga yang belum memiliki definition type yang baik, itupun kita biasanya langsung buat type definition-nya sendiri).
Mari kita mulai dengan trik pertama yang sangat fundamental, tapi sering diabaikan.
*
Trik 1: Menguasai Discriminated Unions dan Type Narrowing
Di dunia nyata, data yang kita terima dari API tidak selalu seragam. Kadang kita menerima objek success, kadang objek error, kadang objek loading. Kalau kamu hanya menggunakan satu tipe besar dengan banyak properti opsional, developer pasti akan bingung dan rawan salah panggil.
Discriminated Unions (Union Terdiskriminasi) adalah solusi elegan untuk masalah ini. Ini adalah cara menggabungkan beberapa tipe menjadi satu, di mana setiap tipe memiliki properti umum yang berfungsi sebagai penanda unik (diskriminator). Penanda ini biasanya berupa string literal seperti status atau type.
Konsep Inti: Penanda Status
Misalnya, kita ingin menangani hasil pemanggilan API.
typescript
interface DataLoading {
status: 'loading';
}interface DataError {
status: 'error';
errorMessage: string;
errorCode: number;
}interface DataSuccess {
status: 'success';
payload: {
id: number;
title: string;
};
}Dengan mendeklarasikan APIResult seperti di atas, kita sudah membuat kode kita sangat aman. Tapi, bagaimana cara menggunakannya? Di sinilah Type Narrowing berperan.
Aplikasi Type Narrowing dengan if
Ketika kita memiliki variabel bertipe APIResult, kita bisa menggunakan pernyataan if atau switch untuk ‘mempersempit’ tipe tersebut. Karena kita tahu properti status pasti ada di semua objek, kita bisa menggunakannya sebagai gatekeeper.
typescript
function handleResult(result: APIResult) {
if (result.status === 'loading') {
// Di sini, TypeScript tahu pasti bahwa 'result' adalah DataLoading
console.log("Data masih dimuat. Mohon tunggu.");
// Kita tidak bisa memanggil result.payload di sini, dan TS akan marah. Aman!Kenapa ini jauh lebih unggul dari any?
- Ekspresif: Kode kita secara eksplisit menunjukkan semua kemungkinan state data.
- Keamanan: Jika kamu mencoba mengakses
errorMessagesaat statusnyasuccess, TypeScript akan langsung memberi tahu kamu bahwa itu adalah kesalahan fatal saat compile time. - Refactoring Mudah: Jika besok kamu menambahkan state baru (misalnya,
status: 'idle'), TypeScript akan langsung menunjuk semua function yang memprosesAPIResultagar kamu tidak lupa menangani state baru tersebut. Ini sangat membantu saat mengembangkan aplikasi skala besar.
*
Trik 2: Memanfaatkan Utility Types (Senjata Rahasia Developer Produktif)
Jika kamu merasa menulis tipe baru untuk setiap variasi data yang kamu butuhkan itu ribet dan repetitif, selamat! Kamu sudah siap menggunakan Utility Types bawaan TypeScript.
Utility Types memungkinkan kita memanipulasi tipe yang sudah ada untuk menciptakan tipe baru, tanpa harus menulis ulang definisinya. Ini adalah fondasi dari DRY (Don't Repeat Yourself) dalam Type-Safety.
Berikut adalah tiga Utility Types yang paling sering kami gunakan di Javapixa untuk mempercepat pengembangan aplikasi dan menjaga konsistensi data:
A. Partial: Membuat Semua Properti Menjadi Opsional
Seringkali, kamu memiliki interface data lengkap (misalnya User), tetapi kamu hanya perlu sebagian kecil properti itu untuk operasi update (misalnya, pengguna hanya ingin mengubah nama dan email, bukan ID atau tanggal pendaftaran).
Daripada membuat interface baru yang isinya hanya nama dan email, kamu bisa menggunakan Partial.
typescript
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}// UpdateUser hanya butuh sebagian properti dari User
type UpdateUserPayload = Partial;
/* Hasil:
{
id?: number;
name?: string;
email?: string;
createdAt?: Date;
}
*/function updateUser(id: number, changes: UpdateUserPayload) {
// Dalam function ini, 'changes' boleh kosong atau hanya berisi sebagian data.
// Tapi kita tahu, jika 'name' ada, ia pasti bertipe string.
console.log(Mengupdate pengguna ${id} dengan data: , changes);
}B. Pick: Memilih Hanya Properti yang Dibutuhkan
Jika kamu hanya ingin mengambil beberapa properti spesifik dari sebuah tipe besar, Pick adalah pilihan terbaik. Ini sangat berguna untuk DTO (Data Transfer Object) agar data yang dikirim ke front-end tidak berlebihan (mengurangi payload).
typescript
interface Product {
id: number;
name: string;
price: number;
stock: number;
supplierId: number;
internalCode: string; // Tidak perlu dikirim ke front-end
}// ProductDisplay hanya butuh 3 properti untuk ditampilkan
type ProductDisplay = Pick;
/* Hasil:
{
id: number;
name: string;
price: number;
}
*/C. Omit: Menghilangkan Properti yang Tidak Diinginkan
Kebalikan dari Pick, Omit berguna ketika kamu ingin menggunakan hampir semua properti dari suatu tipe, kecuali beberapa yang harus dihilangkan (misalnya, properti sensitif atau properti yang di-generate otomatis oleh database).
typescript
// Kita ingin membuat data baru, kita tidak perlu properti 'id' dan 'createdAt'
type NewProductPayload = Omit;
/* Hasil:
{
name: string;
price: number;
stock: number;
supplierId: number;
internalCode: string;
}
*/Dengan menguasai Utility Types, kamu tidak hanya membuat kode lebih rapi, tetapi juga mengurangi duplikasi tipe hingga 80%, yang pada akhirnya membuat refactoring aplikasi menjadi jauh lebih cepat dan aman.
*
Trik 3: Generics Berkelas: Membuat Fungsi Kamu Reusable dan Reliable
Kalau kamu sudah lama di dunia development, kamu pasti sering membuat fungsi yang sangat mirip, hanya berbeda sedikit di tipe datanya. Mungkin kamu punya fetchUsers() dan fetchProducts(). Keduanya melakukan hal yang sama: memanggil API dan mengembalikan data dalam bentuk array.
Jika kamu membuat dua fungsi terpisah, itu berarti kamu melanggar DRY. Tapi jika kamu menggunakan any, kamu merusak Type-Safety. Solusinya? Generics yang diperketat (Constrained Generics).
Generics memungkinkan kamu menulis fungsi atau kelas yang bekerja dengan berbagai tipe data, tanpa kehilangan informasi tipe tersebut. Ini adalah kunci untuk membangun library atau hook yang benar-benar reusable.
Konsep Inti: Placeholder Tipe ()
Generics menggunakan placeholder (sering dilambangkan dengan , , atau ) untuk mewakili tipe yang akan ditentukan saat fungsi dipanggil.
typescript
// Fungsi ini bisa menerima array apa saja, dan mengembalikan elemen pertamafunction getFirstElement(arr: T[]): T | undefined {
return arr[0];}
const users = [{ name: "Budi" }, { name: "Ani" }];
const firstUser = getFirstElement(users); // TypeScript secara ajaib tahu: 'firstUser' pasti bertipe { name: string } | undefined!
const numbers = [1, 2, 3];
const firstNumber = getFirstElement(numbers);// TypeScript tahu: 'firstNumber' pasti bertipe number | undefined!
Hebatnya, kita tidak perlu lagi menggunakan any di dalam fungsi, tapi fungsi ini tetap fleksibel.
Constrained Generics: Membatasi agar Tetap Aman
Fleksibilitas Generics memang keren, tapi kadang kita perlu memastikan bahwa tipe yang dimasukkan (T) memiliki properti tertentu. Misalnya, kita ingin membuat fungsi yang hanya bekerja pada objek yang memiliki properti id.
Di sinilah Constrained Generics masuk, menggunakan kata kunci extends.
typescript
// Kita mendefinisikan tipe dasar yang harus dimiliki oleh T
interface Identifiable {
id: number;}
// T harus 'extend' (memperluas) Identifiable, artinya T wajib punya properti id: numberfunction printId(item: T): void {
// Kita aman memanggil item.id, karena TS menjamin itu ada. console.log(ID dari item ini adalah: ${item.id});}
const product = { id: 50, name: "Laptop" };printId(product); // OK, karena product memiliki 'id'
const simpleName = { name: "Kursi" };// printId(simpleName); // ERROR! TypeScript menolak, karena simpleName tidak memiliki 'id'.
Dengan trik ini, kita berhasil menciptakan fungsi yang:
- Fleksibel (bisa menerima objek apa pun selama memiliki
id). - Aman (kita terhindar dari runtime error karena mencoba mengakses properti yang tidak ada).
Menggunakan Constrained Generics adalah standar di Javapixa Creative Studio, terutama ketika kami membangun library kustom, hook React, atau helper function yang digunakan di seluruh codebase. Ini memastikan bahwa saat tim lain menggunakan library yang kita buat, mereka langsung mendapatkan jaminan Type-Safety penuh.
*
Penutup: Dari any ke Aplikasi Tahan Banting
Mengganti kebiasaan menggunakan any memang butuh usaha di awal. Mungkin terasa lebih lambat karena kamu harus memikirkan struktur tipe dengan lebih detail. Namun, ingatlah bahwa waktu yang kamu habiskan untuk mendefinisikan tipe adalah investasi jangka panjang.
Discriminated Unions, Utility Types, dan Constrained Generics adalah tiga pilar yang akan mengubah kode kamu dari sekadar "berjalan" menjadi "tahan banting" dan "mudah di-maintenance". Kode yang aman adalah kode yang lebih murah untuk dikelola.
Di Javapixa Creative Studio, komitmen kami adalah menyediakan website dan aplikasi yang tidak hanya indah secara desain, tetapi juga kuat dan scalable secara arsitektur. Standar kualitas ini hanya bisa dicapai dengan disiplin Type-Safety yang ketat. Jika kamu siap membawa proyek digitalmu ke level selanjutnya, meninggalkan kebiasaan any adalah langkah pertama yang harus kamu ambil. Ayo, buat kode yang membuat future self kamu bangga!