Typescript Why You Should Avoid Explicit Function Generics If Possible
There are few reasons, the obvious reason is: optimal developer experience
An intelligence type do the most work for you, it is more automate and you write less code
But there is one very important reason: it does not work well with union
to understand this, observe the below example:
type SomeType = {
name: string;
quantity: number;
};
const someFunc = <Key extends keyof SomeType>(
key: Key,
value: SomeType[Key],
) => {
// ...
};
// not explicit generics
someFunc("name", "John") // OK
someFunc("name", 10) // Error as desired
someFunc("quantity", "John") // Error as desired
someFunc("quantity", 10) // OK
// explicit generics
someFunc<"name" | "quantity">("name", 10) // wrong, but no error
now, to be fair, this is not developers fault and should be not developers responsibility
ideally TS should handle case like this correctly, but unfortunately we don’t have much option
except with this magic utility…
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true
type SomeType = {
name: string;
quantity: number;
};
const someFunc = <Key extends keyof SomeType>(
key: IsUnion<Key> extends true ? "No Union!":Key,
value: SomeType[Key],
) => {
// ...
};
someFunc<"name" | "quantity">("name", 10) // error as expected
// still works as normal
someFunc("name", "John") // OK
someFunc("name", 10) // Error as desired
someFunc("quantity", "John") // Error as desired
someFunc("quantity", 10) // OK
Though this is a legit solution, it is not the best solution. As you can see it only warn you about union but do nothing to the other value type. Ideal solution is to show error on the value 10
.
it is also not fun to do this every time, it is also not fun to do this every time, so please avoid explicit generic if possible