It is fairly common to have methods where one or more parameter can be null or undefined (or often both with derived types). This can make for really long method signatures in some cases:
doThing(
user: User | undefined | null,
thing: Thing | undefined | null,
...moreArgs
): Result {
// ...
}
I'm aware of a few ways to try to solve this problem, but both have downsides.
Optional parameters are great if the argument is truly optional, but feel awkward when the passed object can be null.
This is shorter but just looks wrong to me:
doThing(user?: User | null, thing?: Thing | null, ...moreArgs)
The other way I know to fix this is with a generic:
type Nullable<T> = T | null | undefined
doThing(user: Nullable<User>, thing: Nullable<Thing>)
This is reasonable but I find that projects often end up with more than one generic of this type defined, they get imported from third party libraries, derived types end up having a trail of Maybe<T>, Nullable<T>, etc. Trying to keep this use standard across a large project can be really hard.
This seems like such a common use case that I would expect a more consistent solution. Is there a better way to do this?
Unfortunately there is no built-in syntax that acts as a shorthand to writing the union of a type with null and undefined. If you want such behavior, you will need to either do it manually, or define or import a reusable utility type like
type Nullable<T> = T | null | undefined
For better or worse, TypeScript will not introduce such a utility type globally; this gets requested occasionally and consistently declined. See microsoft/TypeScript#39522 for example. Their reasoning is:
What we've heard from users is that they don't like it when we needlessly add new global types because a) it causes conflicts with theirs and b) we don't always pick the same definition that they were using.
Generally speaking they will only introduce utility types if such a type is necessary for automatically producing declaration files. I think the Omit<T, K> utility type was introduced to represent the behavior of the spread operator on generic types, but there were quite a few unhappy real world TS developers who were using their own slightly-different version of Omit which now. You can read more at this twitter thread which explains the pitfalls in more detail.
Since, so far at least, declaration files don't need a utility type to represent | null | undefined (it's hard to envision such a scenario where you couldn't just write | null | undefined in the declaration file), it's not worth it to the TS team to introduce this.
That leaves creating it yourself, or importing it from some well-known TS type library like ts-toolbelt's U.Nullable, or working with the other developers on a large project to adhere to some utility-type-related coding standards. None of these are great, but they're the best you can get in the absence of support from the language itself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With