Understanding Nullish Coalescing in TypeScript 3.7
Let's explore a fresh addition to TypeScript 3.7 that's also making its way into standard JavaScript. This feature is known as Nullish Coalescing, and the TypeScript team provides an official overview in their release notes. If you'd like a deeper dive, stick around.
The Nullish Coalescing Operator
While the world could certainly use more harmony, we'll focus on achieving it in our code. So, what exactly does Nullish Coalescing entail, and what's the motivation behind it?
In essence, it bears some resemblance to this pattern:
const mayBeThisThingOrThisOtherThingIfNot = thisThing || thisOtherThing;
though the comparison isn't entirely accurate.
Developers with C# experience might recognize similar operators from that ecosystem. If that's you, feel free to skip ahead to your next favorite show on <insert your preferred streaming service here>.
For those still with me, let's examine how this operator diverges from the code above. The primary distinction is in its syntax, which looks like this:
const mayBeThisThingOrThisOtherThingIfNot = thisThing ?? thisOtherThing;
If
0or''orfalseor0nor any other falsy condition mentioned previously could hold legitimate value, reach for the Nullish Coalescing operator —??. In situations where those values aren't meaningful, the||operator should suffice and likely deliver the behavior you're after.
Let's unpack both operators by examining their more verbose equivalents:
const mayBeThisThingOrThisOtherThingIfNot = thisThing || thisOtherThing;
which is equivalent to:
if (
thisThing != 0 &&
thisThing != undefined &&
thisThing != false &&
thisThing != '' &&
thisThing != null &&
thisThing != NaN &&
thisThing != 0n
) {
const isThisThing = thisThing; // do stuff with isThisThing;
} else {
const isThisOtherThing = thisOtherThing; // do stuff with isThisOtherThing
}
on the other hand,
const mayBeThisThingOrThisOtherThingIfNot = thisThing ?? thisOtherThing;
could be expressed as:
if (thisThing != null && thisThing != undefined) {
const isThisThing = thisThing; // do stuff with isThisThing;
} else {
const isThisOtherThing = thisOtherThing; // do stuff with isThisOtherThing
}
Quite a difference in the underlying conditions! Naturally, we Type/JavaScript developers are completely at ease handling falsy values (or truthy ones, if you prefer) and would never stumble over the classic zero as a valid value scenario. However, those transitioning from different programming backgrounds might find it less obvious. Here's some guidance:
When 0, '', false, 0n, or any other falsy value mentioned above represents a valid option, opt for the Nullish Coalescing operator — ??. Conversely, if those values don't hold significance, || should work perfectly well and deliver the intended outcome.
With the runtime behavior covered, you might be wondering where TypeScript fits into this discussion. Fair point — let's shift focus to that now.
Let's examine how this operator influences type inference. Rather than using the contrived examples above, we'll work with a more realistic scenario.
First, consider a situation where this operator probably isn't the best fit.
Imagine a function responsible for retrieving the status of an entity based on its identifier:
function getState(id: number) {
const stateId = id ?? '123';
return this.database.find(stateId);
// compilation error if this.database.find requires number
}
Notice that TypeScript will raise an error if this.database.find expects a number for its id parameter, since stateId is typed as number | '123'. This illustrates that combining two distinct types with this operator might not be ideal, as Type Guards would become necessary to distinguish between the possible types.
A more typical, and arguably intended, application involves initializing variables with fallback values when none are supplied. Let's revisit the earlier example, this time ensuring consistent types on both sides.
function getState(id: number) {
const defaultStateId = 1;
const stateId = id ?? defaultStateId;
return this.database.find(stateId); // no compilation error thrown
}
It's worth noting that we deliberately avoid the || operator here, since zero (0) could represent a valid identifier. Using || would result in returning the default state instead of the actual first state, which is clearly not what we want!
That covers the essentials of this useful operator. While || has served us well, the nullish coalescing variant addresses those peculiar edge cases where falsy values carry legitimate meaning. Happy coding!
