Any vs Generic Type vs Unknown Typescript

2.6k Views Asked by At

Can the Generic Types replace the type any?

I know that it is hardly recommended to avoid the use of type any since it removes all type checking provided by typescript. In other words, it is not safe.

What is the better approach, to use unknown or generic types instead of any?

Example of function signature I want to adjust:

getTranslationByKeys(keys: any): any {}

The type of the keys and the return value are specified on the function's call (array, object...)

1

There are 1 best solutions below

0
On

When it comes to function definitions, I think the blanket prohibition on using any, -- often stated as: "don't ever use it unless it's temporarily to help with a migration of javascript to typescript" -- is oversimplified. Specifically, I don't think the prohibition against any is nearly as strong when it is used as the constraint on a generic type parameter to a function. Indeed, I think any can be quite a useful type when used in that context.

I don't disagree that any is quite bad in the example you give.

getTranslationByKeys(keys: any): any {}

This is just completely opting out of the type system. For example, look at this call site:

const myResult = getTranslationByKeys(keys: [1,3,4])

I have no idea whether this function can execute on the parameters I've passed it or what myResult will be when its done (if it even can complete):

Bad; for sure. But consider the following function typing:

function getTranslationByKeys<T extends any>(keys: T): T {}

It's got the word any in it. And certainly there are many lint rules that will yell at you for using this, and many texts on learning TypeScript that would lead you to believe this is a bad idea unless you are doing it temporarily for purposes of an upgrade.

But I don't think that's really right in this case. Why isn't that a problem? Because unlike when you use any as a raw type, in this case you are not completely opting out of the type system. You will, in fact, still have strong type safety at the call site. For example, a call like:

const myResult = getTranslationByKeys(keys: [1,3,4])

will be definitely typed. myResult will have type number[], and (at least I would argue), the lack of a constraint on keys is an explicit signal to the consumer that it is ok to pass in any type for the parameter.

Now, you have given up some type-safety inside the function definition, in the sense that inside the function definition keys could literally be anything. But sometimes that's what you want. You may have a function that can, in fact, handle any type of input, like this (admittedly silly) function:

function getTranslationByKeys<T extends any>(keys:T):T{ 
  console.log(keys)
  return keys
}

While that particular code might be silly, my point is that I don't see anything about the typing of that code that is problematic. The typing will be strong at the call site and the code is accurately typed in the function definition. Even though the dreaded any is used!

Someone could point out that what I've written above is really the same as just writing getTranslationByKeys<T> which you see all the time and, technically, doesn't have the word any in it. Fine, but that doesn't change anything above. The two statements are equivalent and one shouldn't be better just because the word any is hidden.

Moreover, any can be used in much more useful ways in generic constraints. Consider the following example:

function getTranslationByKeys<T extends Record<string,any>>(keys:T):T {}

This too contains the dreaded word any but it is a highly useful type of constraint. It says that T can be any object so long as it is, in fact, an object with string indices. That's hardly unconstrained and is actually far more precise than if you had said T extends object. I see and use this use of any all the time in function definitions.

Now, could you use unknown here instead of any? Maybe. I confess I haven't used unknown enough with generic constraints to know how equivalent its behavior is. But, to me at least, unknown seems less descriptive of what you are trying to convey in the situation above. At least from the perspective of the consumer of your function, you aren't intending to tell them "you can give me a string-indexed object with unknown type values", you actually mean "you can give me a string-indexed object with any values"--which is exactly what the word any conveys!

So, my own opinion is that any, when used in the context of constraints on generic type parameters, can be quite useful and descriptive, and it should not be subject to the type of blanket prohibition one often hears for using any as a simple type.

I'd be very interested to hear what others think on this topic.