There are situations where we have to plan for, and deal with
the possibility that values are null
or undefined
. In this chapter
we’ll dive deep into null, undefined, definite assignment, non-nullish
coalescing, optional chaining and the non-null assertion operator.
Although null
, void
and undefined
are all used to describe “nothing” or “empty”,
they are independent types in TypeScript. Learning to use them to your advantage, and they can be powerful tools for clearly expressing your intent as a code author.
null
null
means: there is a value, and that value is nothing. While some people believe that null is not an important part of the JS language, I find that it’s useful to express the concept of a “nothing” result (kind of like an empty array, but not an array).
This nothing is very much a defined value, and is certainly a presence — not an absence — of information.
ts
const userInfo = {name: "Mike",email: "mike@example.com",secondaryEmail: null, // user has no secondary email}
undefined
undefined
means the value isn’t available (yet?)
In the example below, completedAt
will be set at some point
but there’s a period of time when we haven’t yet set it. undefined
is an unambiguous indication that there may be something different there in the future:
ts
const formInProgress = {createdAt: new Date(),data: new FormData(),completedAt: undefined, //}function submitForm() {formInProgress.completedAt = new Date()}
void
We have already covered this in the functions chapter, but as a reminder:
void
should exclusively be used to describe that a function’s return value should be ignored
tsTry
console .log (`console.log returns nothing.`)
Non-null assertion operator
The non-null assertion operator (!.
) is used to cast away the possibility
that a value might be null
or undefined
.
Keep in mind that the value could still be null
or undefined
, this
operator just tells TypeScript to ignore that possibility.
If the value does turn out to be missing, you will get the familiar cannot call foo on undefined
family of errors at runtime:
tsTry
typeGroceryCart = {fruits ?: {name : string;qty : number }[]vegetables ?: {name : string;qty : number }[]}constcart :GroceryCart = {}'cart.fruits' is possibly 'undefined'.18048'cart.fruits' is possibly 'undefined'.. cart .fruits push ({name : "kumkuat",qty : 1 })cart .fruits !.push ({name : "kumkuat",qty : 1 })
I recommend against using this in your app or library code, but
if your test infrastructure represents a throw
as a test failure (most should)
this is a great type guard to use in your test suite.
In the above situation, if fruits
was expected to be present and it’s not,
that’s a very reasonable test failure
Definite assignment operator
The definite assignment !:
operator is used to suppress TypeScript’s
objections about a class field being used, when it can’t be proven1
that it was initialized.
Let’s look at the following example:
tsTry
classThingWithAsyncSetup {setupPromise :Promise <any> // ignore the <any> for nowProperty 'isSetup' has no initializer and is not definitely assigned in the constructor.2564Property 'isSetup' has no initializer and is not definitely assigned in the constructor.: boolean isSetup constructor() {this.setupPromise = newPromise ((resolve ) => {this.isSetup = falsereturn this.doSetup (resolve )}).then (() => {this.isSetup = true})}private asyncdoSetup (resolve : (value : unknown) => void) {// some async stuff}}
TypeScript is warning me that someone could create an instance of this class
and immediately attempt to access .isSetup
before it gets a boolean value
tsTry
letmyThing = newThingWithAsyncSetup ()myThing .isSetup // what if this isn't assigned yet?
What I know (that the compiler doesn’t) is that the function passed into the
Promise
constructor is invoked synchronously, meaning by the time we
receive our instance of ThingWithAsyncSetup
, the isSetup
property will
most certainly have a value of false
.
This is a good example of a totally appropriate use of the definite assignment operator, where I as the code author have some extra context that the compiler does not.
-
Where “proven” means, “the compiler can’t convince itself.”
↩