Hey, I have a function and I want to make its parameter accept any types exceptnumber. How can I achieve that?
Sometimes you might need this, so let’s think about it.
First you may think about the built-in utility type Exclude, which allows you to exclude specific types from a union type. However, it doesn’t work for this case because it requires a union type to exclude from, but unfortunately any is NOT a union type.
bad-example.ts
declarefunction
functionfoo(arg:Exclude<any, number>):void
foo(
arg: any
arg:
typeExclude<T, U> =TextendsU?never:T
Exclude from T those types that are assignable to U
Exclude<any, number>):void
functionfoo(arg:Exclude<any, number>):void
foo('string') // ✅
functionfoo(arg:Exclude<any, number>):void
foo(true) // ✅
functionfoo(arg:Exclude<any, number>):void
foo(123) // ✅ this still works and raises no error 😢
As you can see, passing a number to the foo function does not raise any error, because Exclude<any, number> resolves to any. To address this, we need to narrow the type of the parameter to exclude number specifically.
We can introduce a generic type which will automatically inferred as the passed in type, and checks if it assignable to number. If it is, we can return never, which will effectively ban the type from being passed to the function.
The built-in utility type Exclude is an alias of what we are trying to do above, so we can write it in both ways:
Error ts(2345) ― Argument of type '"string"' is not assignable to parameter of type 'number | boolean | null'.
// ⚠️ Passing `any` to the second generic parameter won't work
functionbanTypes<string, any>(arg:any):void
banTypes<string, any>('string')
Caution
In order to take advantage of this, you have to explicitly pass the second generic type to limit the type of the parameter, unless you give it a default type like T = SomeType.
You CANNOT use T = any because it will resolve to any as the parameter’s type, hence passing anything as the argument is acceptable.