👋 Hey folks! In this post, I want to cover the topic of function overloading and explain why it's essential to understand this concept, and why you shouldn't be afraid to use it!
📃 Wiki Definition
Function overloading or method overloading is the ability to create multiple functions of the same name with different implementations. Calls to an overloaded function will run a specific version of that function appropriate to the context of the call.
🧐 Wiki Definition Explanation
In short function overloading is a function with multiple call signatures. This means there are multiple ways to call a function.
Example:
Let's take a look at the example of a function which might be called in a few ways with different arguments
type ProductParams = {
promo?: boolean
isArchived?: boolean;
};
function getProducts(params: ProductParams) {
const url = new URL('/api/v1/products');
const urlParams = new URLSearchParams();
if ('promo' in params) {
urlParams.append('promo', params.promo.toString());
}
if ('isArchived' in params) {
urlParams.append('isArchived', params.isArchived.toString());
}
url.search = urlParams.toString();
return fetch(url.toString()).then(res => res.json());
}
You can call this function
in 2 different ways, with 2 different params. Our function will return from the API
products that might be promo
or archived
, or both promo
and archived
.
🔍 Multiple Function Signatures
Typescript's approach to function overloading is quite distinct from some other languages. Instead of having multiple function definitions, TypeScript uses multiple function signatures followed by a single function body.
The type-checker evaluates these signatures from top to bottom. This order matters because TypeScript will use the first signature that matches the function call. For instance, if a function call can match two overloaded signatures, TypeScript will choose the one that's listed first. Therefore, it's advisable to list more specific signatures higher and keep broader ones below.
💻 Let's implement the overloading for the example function
// Product Interfaces
interface PromoProducts {
//... some properties specific to promo products
}
interface ArchivedProducts {
//... some properties specific to archived products
}
interface PromoAndArchivedProducts {
//... some properties combining both promo and archived products
}
type ProductParams = {
promo?: boolean
isArchived?: boolean;
};
// Function overloading signatures
function getProducts(params: { promo: true }): Promise<PromoProducts>;
function getProducts(params: { isArchived: true }): Promise<ArchivedProducts>;
function getProducts(params: { promo: true, isArchived: true }): Promise<PromoAndArchivedProducts>;
function getProducts(params: ProductParams) {
const url = new URL('/api/v1/products');
const urlParams = new URLSearchParams();
if ('promo' in params) {
urlParams.append('promo', params.maxPrice.toString());
}
if ('isArchived' in params) {
urlParams.append('isArchived', params.isArchived.toString());
}
url.search = urlParams.toString();
return fetch(url.toString()).then(res => res.json());
}
After the implementation, we will be able to use our function
in different ways and have a type of safety in place.
🏁 Finish
I recommend not being afraid to use function overloading. It's so helpful when you're providing type-rich interfaces and aiming for better code readability and safety.
Top comments (0)