Kamu Masih Pakai `Any` di TypeScript Coba Tiga Cara Ini Biar Type Safety Kamu Maksimal

Kamu Masih Pakai `Any` di TypeScript Coba Tiga Cara Ini Biar Type Safety Kamu Maksimal
Photo by Brett Jordan/Unsplash

Mungkin kamu pernah dengar pepatah di dunia TypeScript: any itu ibarat jalan pintas menuju neraka maintenance. Jujur saja, kita semua pernah menggunakannya. Ketika deadline sudah di depan mata dan TypeScript rewel minta type yang rumit, menjentikkan any terasa seperti solusi paling cepat, paling mudah, dan paling tidak menyakitkan.

Masalahnya, begitu kamu pakai any, kamu langsung mengibarkan bendera putih di hadapan Type Safety. TypeScript, yang seharusnya menjadi pelindungmu dari error konyol saat runtime, tiba-tiba mogok kerja. Kamu kehilangan IntelliSense, kamu kehilangan refactoring yang aman, dan kamu membuka pintu lebar-lebar bagi bug yang baru muncul setelah kode kamu live di production.

Jika kamu serius ingin menjadi developer modern yang handal—dan jika kamu ingin kode yang kamu tulis bisa diskalakan dan mudah di-maintain—saatnya kita tinggalkan kebiasaan buruk itu. Tim di Javapixa Creative Studio sangat menganjurkan untuk memprioritaskan Type Safety maksimal, karena kami tahu dampaknya pada kualitas dan kecepatan pengembangan jangka panjang.

Ini bukan sekadar tentang menghapus any. Ini tentang menggantinya dengan alat yang lebih canggih.

Berikut adalah tiga teknik utama (ditambah satu bonus modern) yang akan membuat kode TypeScript-mu bersih, aman, dan sangat profesional.

---

1. Upgrade Pertahananmu: Ganti any dengan unknown

Ketika kamu bingung tipe data apa yang akan datang, godaan untuk menggunakan any sangat besar. Tapi coba pikirkan ini: any berarti "anggap aku tahu apa yang aku lakukan, abaikan semua pemeriksaan." Sementara unknown berarti, "Aku tahu ada sesuatu yang datang, tapi aku harus memeriksanya dengan ketat sebelum menggunakannya."

Inilah perbedaan fundamental antara keduanya:

any: Kamu bisa melakukan apa saja padanya. Memanggil properti, menjalankan fungsi, tanpa diperiksa oleh compiler*. unknown: Kamu tidak bisa melakukan apa-apa padanya sampai kamu membuktikan tipenya kepada compiler*.

Mengapa unknown Lebih Baik?

unknown memaksa kita melakukan type narrowing—proses di mana kita membuktikan tipe data yang dimaksud. Ini adalah lapisan keamanan ekstra yang mutlak diperlukan, terutama saat berhadapan dengan data yang datang dari luar ekosistem TypeScript (seperti input pengguna, API, atau data yang di-parse dari JSON).

Contoh Kasus: Fungsi yang Menerima Input Apapun

Misalkan kamu punya fungsi yang bisa menerima input apapun dan mencoba memprosesnya:

Kode Buruk (Menggunakan any):

typescript
function prosesInputAny(data: any) {
    // TypeScript tidak peduli apakah data punya properti 'id' atau tidak.
    // Jika data adalah string, kode ini akan crash saat runtime.
    console.log(data.id); 
}
// Tidak ada error saat kompilasi, tapi potensi crash saat runtime tinggi.
prosesInputAny("Halo"); // RUNTIME ERROR: Cannot read properties of undefined (reading 'id')

Kode Baik (Menggunakan unknown):

typescript
function prosesInputUnknown(data: unknown) {
    // TypeScript akan marah di sini:
    // console.log(data.id); // Error: Object is of type 'unknown'.

Menggunakan unknown adalah langkah pertama dan termudah untuk meningkatkan Type Safety-mu secara drastis, karena ia mengubah mindset dari "percayai aku" menjadi "buktikan dulu."

---

2. Pahlawan Reusabilitas: Memanfaatkan Generics

Salah satu alasan terbesar developer menggunakan any adalah saat mereka menulis fungsi yang sangat fleksibel dan dapat bekerja dengan berbagai tipe data, namun tipe data output harus sama dengan tipe data input.

Contoh klasiknya adalah fungsi "identity" (fungsi yang mengembalikan apa pun yang diterimanya) atau, yang lebih realistis, fungsi untuk mengambil data (fetching data).

Jika kamu menggunakan any di sini, kamu kehilangan semua informasi tipe yang dibawa oleh data yang masuk atau keluar. Solusinya? Generics.

Generics memungkinkan kamu menulis komponen atau fungsi yang beroperasi pada berbagai tipe data tanpa kehilangan informasi tipe yang spesifik. Anggap saja Generics (, , ) sebagai placeholder untuk tipe yang akan ditentukan saat fungsi itu dipanggil.

Contoh Kasus: Fungsi Fetching Data

Misalnya kamu membuat utility function untuk memanggil API. Kamu tahu responsnya akan berupa JSON, tapi struktur JSON itu bervariasi—kadang daftar pengguna, kadang detail produk, kadang pesan error.

Kode Buruk (Menggunakan any):

typescript
async function fetchDataAny(url: string): Promise {
const response = await fetch(url);
return response.json();
}
// Saat memanggilnya:
const dataPengguna = await fetchDataAny('/users');
// dataPengguna adalah 'any', kamu harus mengingat strukturnya.
console.log(dataPengguna.namaLengkap); // Tidak ada peringatan jika 'namaLengkap' salah.

Kode Terbaik (Menggunakan Generics):

typescript
interface User {
id: number;
username: string;
}

// adalah Generic Type Parameter. Kita berjanji bahwa T adalah tipe kembalian.
async function fetchData(url: string): Promise {
const response = await fetch(url);
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status});
}
return response.json() as T;
}

// Saat memanggilnya, kita memberitahu TypeScript tipe apa yang kita harapkan:
const dataPengguna = await fetchData('/users');

// Sekarang, dataPengguna adalah User[], dan kita mendapatkan Type Safety penuh!
dataPengguna.forEach(user => {
// Type checking dan Intellisense bekerja sempurna di sini
console.log(user.username);
});

Generic Constraints

Kadang, kamu perlu fungsi Generics yang bisa bekerja dengan banyak tipe, tetapi hanya tipe yang memiliki properti tertentu. Di sinilah Generic Constraints berperan, menggunakan extends.

Misalnya, kita hanya ingin fungsi getObjectKey bekerja pada objek:

typescript
// T harus berupa object
function getObjectKey(obj: T, key: K): T[K] {
return obj[key];
}

const user = { name: "Budi", age: 25 };

// Bekerja, karena T = { name: string, age: number }, K = 'name'
const userName = getObjectKey(user, "name");

// Ini akan error saat kompilasi, karena 123 bukan object dan tidak ada 'name'
// getObjectKey(123, "name"); // Type error!

Dengan menguasai Generics, kamu menghilangkan kebutuhan untuk menggunakan any pada hampir semua fungsi utility atau komponen React yang dirancang untuk digunakan kembali (reusable).

Tim pengembangan di Javapixa Creative Studio sangat bergantung pada Generics untuk memastikan shared components dan service layer kami tetap fleksibel tanpa mengorbankan keamanan tipe, yang merupakan kunci dalam membangun arsitektur aplikasi yang kompleks.

---

3. Mengelola Kompleksitas Data: Discriminated Unions dan Type Narrowing

Di dunia nyata, data API seringkali tidak seragam. Misalnya, respons dari server mungkin berupa objek Success atau objek Error. Jika kamu menggunakan any untuk menampung kedua kemungkinan ini, kamu harus menebak properti mana yang ada di runtime.

Solusinya adalah menggunakan Discriminated Unions—sebuah teknik canggih yang membuat TypeScript mengerti struktur data yang berbeda ini, asalkan setiap struktur memiliki satu properti pembeda (diskriminator).

Contoh Kasus: Respons API Gabungan

Misalnya, kita mendefinisikan dua kemungkinan bentuk respons:

typescript
// 1. Definisikan type diskriminator (harus literal string/number)
type StatusSuccess = {
status: "success"; // DISCRIMINATOR
data: { name: string; email: string };
code: 200;
}

type StatusError = {
status: "error"; // DISCRIMINATOR
errorMessage: string;
errorCode: number;
}

// 2. Gabungkan keduanya dalam Union Type
type ApiResponse = StatusSuccess | StatusError;

Sekarang, kita bisa menulis fungsi yang menangani ApiResponse tanpa any, dan TypeScript akan membantu kita.

typescript
function handleResponse(response: ApiResponse) {
// Kita gunakan properti 'status' sebagai penentu (discriminator)
if (response.status === "success") {
// Di sini, TypeScript tahu pasti bahwa 'response' adalah StatusSuccess.
// Properti 'errorMessage' dan 'errorCode' tidak bisa diakses di scope ini.
console.log(Pengguna berhasil dimuat: ${response.data.name});
console.log(Kode HTTP: ${response.code});
} else {
// Di sini, TypeScript tahu pasti bahwa 'response' adalah StatusError.
// Properti 'data' dan 'code' tidak bisa diakses.
console.error(Terjadi kesalahan: ${response.errorMessage});
console.warn(Kode Kesalahan: ${response.errorCode});
}

// Alternatif menggunakan switch case (seringkali lebih rapi):
switch (response.status) {
case "success":
// ...aman menggunakan response.data
break;
case "error":
// ...aman menggunakan response.errorMessage
break;
// PENTING: Tambahkan exhaustive checking jika ingin Type Safety maksimal
default:
const _exhaustiveCheck: never = response;
return _exhaustiveCheck;
}
}

Discriminated Unions adalah senjata rahasia untuk menangani state aplikasi yang kompleks (seperti state loading, sukses, error di Redux atau Zustand) dan memastikan bahwa saat kamu mengakses properti, properti itu memang benar-benar ada di runtime. Ini jauh lebih kuat dan lebih mudah di-maintain daripada menggunakan any dan serangkaian pengecekan if manual.

---

4. Bonus Level Up: Memanfaatkan Utility Types dan satisfies

Seiring berkembangnya TypeScript, muncul alat-alat baru yang membantu kita menulis kode yang lebih ringkas dan tetap type-safe, mengurangi godaan untuk menggunakan any.

A. Utility Types (Partial, Omit, Pick, dll.)

Saat kamu ingin memodifikasi tipe yang sudah ada (misalnya, membuat semua properti menjadi opsional, atau hanya memilih beberapa properti), jangan buat tipe baru dari nol. Gunakan Utility Types.

Misalnya, kita punya tipe Product:

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

// Ingin membuat fungsi update yang hanya menerima properti opsional
type ProductUpdateInput = Partial;
/ Hasil: { id?: number; name?: string; price?: number; description?: string; } /

// Ingin membuat tipe hanya dengan id dan name
type ProductSummary = Pick;
/ Hasil: { id: number; name: string; } /

// Ingin menghilangkan properti description
type ProductNoDesc = Omit;
/ Hasil: { id: number; name: string; price: number; } /

Menggunakan Utility Types membuat kode lebih jelas, DRY (Don't Repeat Yourself), dan menjamin konsistensi tipe.

B. Operator satisfies (TS 4.9+)

Salah satu skenario umum di mana any muncul adalah saat mendefinisikan objek konfigurasi yang besar. Kamu ingin objek tersebut mematuhi sebuah antarmuka (interface) untuk memastikan strukturnya benar, tetapi kamu juga ingin TypeScript mempertahankan tipe literal yang sangat spesifik di dalamnya (misalnya, nilai string tertentu atau tuple tertentu).

Jika kamu menggunakan type assertion (as Config), TypeScript akan memaksakan tipe dan melupakan detail literal di dalamnya. Jika kamu menggunakan penugasan biasa (: Config), kamu akan kehilangan informasi tipe spesifik.

Inilah mengapa satisfies sangat revolusioner:

typescript
interface ColorConfig {
primary: string;
secondary: string;
}

const config = {
primary: "red", // Tipe literal 'red'
secondary: "blue", // Tipe literal 'blue'
// Coba tambahkan properti yang tidak ada di ColorConfig, ini akan error!
// tertiary: "green", // Error karena tidak ada di ColorConfig
} satisfies ColorConfig;

// KEUNTUNGAN: Kita mendapatkan Type Safety DARI ColorConfig,
// tetapi kita tetap mempertahankan tipe literal yang spesifik:
type PrimaryType = typeof config.primary; // Hasil: 'red' (bukan hanya string)

// Dengan tipe string literal ini, kamu bisa menggunakan
// config.primary di tempat yang memerlukan tipe 'red' spesifik.

Menggunakan satisfies adalah cara modern untuk memastikan struktur objekmu benar tanpa mengorbankan type inference yang detail. Ini adalah praktik yang sangat kami anjurkan di Javapixa Creative Studio untuk pengelolaan configuration dan dictionary besar.

Kesimpulan: Kenapa Repot-repot Meninggalkan any?

Meninggalkan any memang butuh usaha di awal. Itu berarti kamu harus meluangkan waktu ekstra untuk mendefinisikan tipe, memahami Generics, dan merancang data structure dengan Discriminated Unions.

Namun, investasi waktu ini akan terbayar lunas ribuan kali lipat dalam jangka panjang. Ketika proyekmu berkembang menjadi ratusan ribu baris kode, dan developer baru bergabung, mereka tidak perlu menebak-nebak tipe data. Mereka hanya perlu mengandalkan compiler TypeScript untuk memandu mereka.

Type Safety maksimal bukan hanya jargon; itu adalah fondasi untuk maintainability, scalability, dan developer experience yang luar biasa. Jika kamu membangun aplikasi yang ambisius, baik itu SaaS, e-commerce, atau enterprise system, menggunakan TypeScript dengan kedisiplinan penuh adalah keharusan.

Jika kamu mencari mitra pengembangan yang mengimplementasikan standar Type Safety tertinggi, mulai dari Generics hingga Discriminated Unions, Javapixa Creative Studio siap membantu memastikan aplikasi kamu tidak hanya fungsional tetapi juga dibangun dengan arsitektur yang kokoh dan bebas dari jebakan any. Ayo, saatnya kita buat kode yang lebih bersih, lebih aman, dan lebih profesional.