Function overload artinya satu fungsi punya beberapa signature: pasangan "kalau yang masuk ini, yang keluar itu". Signature adalah satu baris berisi nama fungsi, tipe parameter, dan tipe return-nya, tanpa isi. Mirip papan menu di warung: "pesan satu nomor, dapat satu piring; pesan beberapa nomor, dapat satu nampan". Pembeli cuma membaca papan menunya. Dapur yang benar-benar memasak gak kelihatan dari luar.
🧪 Tebakan cepat: fungsi
pesanmenerimanumber | number[]dan mengembalikanstring | string[]. Kamu memanggilpesan(0), lalu.toUpperCase()pada hasilnya. TypeScript mengizinkan atau menolak? Tebak dulu, jawabannya ada di bagian "Masalah kalau cuma pakai union".
Masalah kalau cuma pakai union
Warung kita punya tiga menu. Nomor menunya adalah index array, jadi 0 itu Kopi dan 2 itu Roti.
const MENU = ["Kopi", "Teh", "Roti"];
function pesan(nomor: number | number[]): string | string[] {
if (Array.isArray(nomor)) {
return nomor.map((n) => MENU[n]); // daftar nomor, daftar nama
}
return MENU[nomor]; // satu nomor, satu nama
}
const hasil = pesan(0); // hasil: string | string[]
hasil.toUpperCase();
error TS2339: Property 'toUpperCase' does not exist on type 'string | string[]'.
Property 'toUpperCase' does not exist on type 'string[]'.
TypeScript menolak. Itu jawaban tebakannya. Kamu tahu pesan(0) pasti mengembalikan satu teks, tapi tipenya cuma bilang "string atau array string". Array gak punya .toUpperCase(), jadi TypeScript gak mau ambil risiko. Informasi "satu masuk, satu keluar" hilang di union.
Array.isArray(nomor)mengecek apakahnomorsebuah array. Di dalamif-nya TypeScript tahunomoradalahnumber[], di luarnyanumber. Ini narrowing yang sudah kamu kenal.
Menulis overload
Overload mengembalikan informasi itu. Tulis dulu signature untuk pemanggil, lalu satu implementasi di paling bawah:
const MENU = ["Kopi", "Teh", "Roti"];
// Overload signature: untuk pemanggil, tanpa isi
function pesan(nomor: number): string;
function pesan(nomor: number[]): string[];
// Implementasi: satu-satunya yang punya isi
function pesan(nomor: number | number[]): string | string[] {
if (Array.isArray(nomor)) {
return nomor.map((n) => MENU[n]);
}
return MENU[nomor];
}
const satu = pesan(0); // satu: string
const dua = pesan([0, 2]); // dua: string[]
satu.toUpperCase(); // "KOPI", sekarang boleh
dua.join(", "); // "Kopi, Roti"
- Dua baris pertama adalah overload signature: diakhiri titik koma, tanpa
{ }. Inilah papan menunya. - Baris ketiga adalah implementation signature, milik implementasi. Tipenya harus mencakup semua overload:
number | number[]masuk,string | string[]keluar. - Saat kamu memanggil
pesan(...), TypeScript mencoba overload dari atas ke bawah dan memakai yang pertama cocok.
| Panggilan | Overload yang cocok | Tipe hasil | Nilai saat jalan |
|---|---|---|---|
pesan(0) |
(nomor: number): string |
string |
"Kopi" |
pesan([0, 2]) |
(nomor: number[]): string[] |
string[] |
["Kopi", "Roti"] |
pesan("0") |
gak ada | error | ditolak sebelum jalan |
error TS2769: No overload matches this call.
The last overload gave the following error.
Argument of type 'string' is not assignable to parameter of type 'number[]'.
Setelah di-compile, overload signature hilang semua. JavaScript cuma punya satu fungsi pesan, yaitu implementasinya. Overload murni urusan pengecekan tipe.
Aturan menulisnya
- Semua overload signature ditulis sebelum implementasi, berurutan, dan implementasinya langsung di bawahnya. Kalau implementasi ditaruh di atas, TypeScript protes:
Function implementation is missing or not immediately following the declaration.(ts(2391)). - Implementasi harus cocok dengan semua overload. Kalau kamu menambah
function pesan(nomor: boolean): string;padahal implementasi cuma menerimanumber | number[], munculThis overload signature is not compatible with its implementation signature.(ts(2394)). - Implementation signature gak bisa dipanggil dari luar. Pemanggil cuma melihat overload. Jadi nilai bertipe
number | number[]ditolak, karena gak cocok dengan overload mana pun. - Yang spesifik di atas, yang umum di bawah. TypeScript berhenti di overload pertama yang cocok, jadi overload umum yang ditaruh duluan menutupi yang di bawahnya.
Kapan union saja sudah cukup
Overload berguna kalau tipe hasilnya bergantung pada tipe input: satu nomor jadi satu teks, daftar nomor jadi daftar teks. Kalau hasilnya selalu sama apa pun inputnya, union biasa sudah cukup dan lebih ringkas:
function panjang(isi: string | number[]): number {
return isi.length; // string dan array sama-sama punya length
}
panjang("halo"); // 4
panjang([1, 2, 3]); // 3
Output-nya selalu number, jadi gak ada informasi yang hilang. Patokannya: output beda per tipe input, pakai overload. Output sama untuk semua input, pakai union.
⚠️ Jebakan yang sering ketemu
- Implementasi ditulis duluan. Overload signature di atas, implementasi paling bawah. Kalau terbalik, muncul ts(2391).
- Mengirim nilai union ke fungsi overload. Di dalam
function pesanDari(pilihan: number | number[]), panggilanpesan(pilihan)ditolak denganNo overload matches this call.Kalau memang perlu, tambahkan overload ketiga:function pesan(nomor: number | number[]): string | string[];. - Overload umum di atas yang spesifik. Dengan
function label(x: number | string): string;lalufunction label(x: number): number;, panggilanlabel(5)bertipestring. Overload kedua gak pernah terpakai. - Overload padahal output-nya sama. Tiga overload yang semuanya mengembalikan
stringcukup diganti satu union.
TL;DR: Function overload memberi satu fungsi beberapa signature, supaya tipe hasilnya ikut tipe input: pesan(0) bertipe string, pesan([0, 2]) bertipe string[]. Tulis semua overload signature dulu (tanpa isi, diakhiri titik koma), lalu satu implementasi di paling bawah yang tipenya mencakup semuanya. Pemanggil cuma melihat overload, bukan implementasi, dan TypeScript memakai overload pertama yang cocok, jadi yang spesifik di atas. Setelah compile, overload hilang. Kalau output-nya sama untuk semua input, union biasa sudah cukup.