You sure know that any in TypeScript is bad and should be avoided. But what should you replace it with?

When the type is unknown throughout the application: If the type is unknown in the entire application, such as when receiving data from an untrusted third-party API, the unknown type is your best choice. It's the type-safe counterpart of any and informs TypeScript that the type is unknown until you validate it at runtime. You can validate the type using methods like Array.isArray() or the typeof operator:

function foo(value: unknown) {
if (Array.isArray(value)) {
// We know it's an array
console.log("Number of items: " + value.length);
} else if (typeof value === "string") {
// We know it's a string
console.log(value.toLowerCase());
}
}

When the type is unknown in a specific context: Sometimes, your type might be unknown in one part of the application (where you don't care about the type) but known in another part. If you try to replace any with unknown in this case, you will likely encounter errors.

const addValidationFn = (fn: ((value: unknown) => boolean)) => { /* ... */ };

// => Doesn't work, you will get: Type 'unknown' is not assignable to type 'number'
addValidationFn((value: number) => { /* ... */ });

In this case, TypeScript is correct. Inside addValidationFn, you could call the provided function with any value because you've specified that the type is unknown, and any value is assignable to unknown. Therefore, providing a function that expects a number is not valid.

In such scenarios, you should use generics:

const addValidationFn = <V,>(fn: ((value: V) => boolean)) => { /* ... */ };

// => This works
addValidationFn((value: number) => { /* ... */ });

Generics allow you to capture the type information while maintaining type safety. In the above example, V is a placeholder for any type, and it will be determined when addValidationFn is called.