working-with-null-and-undefined
Understanding null and undefined:
null
is when you know something is empty and you've marked it as such. undefined
is when something hasn't been given a value yet. They're like two different ways of saying "no data": one is intentional, and the other is just not filled in yet.
Strict Null Checks:
By enabling strict null checks, you're making sure that you don't accidentally use null
or undefined
where a real value is needed. It's a precaution to prevent mistakes.
{
"compilerOptions": {
"strictNullChecks": true
}
}
Union Types with null/undefined:
Union types let you say "this can be a number, or it can be null
, or it can be undefined
". It's explicitly stating all the possibilities for what a value can be.
let age: number | null | undefined;
age = null; // Okay
age = undefined; // Okay
age = 25; // Also okay
Optional Parameters and Properties:
Optional properties in TypeScript are like saying, "You can give me this information if you have it, but it's okay if you don't."
function greet(name: string, greeting?: string) {
// greeting is optional, it can be a string or undefined.
console.log(`${greeting || 'Hello'}, ${name}`);
}
Definite Assignment Assertions:
Definite assignment assertions are like promising TypeScript that you will assign a value later on. It's a way to tell TypeScript, "Trust me, I've got this covered."
let myVar!: number;
initialize();
console.log(2 * myVar); // TypeScript trusts that myVar is assigned.
function initialize() {
myVar = 10; // Assignment happens here.
}
Type Guards for null/undefined:
Type guards are like quality checks. They make sure you're not working with null
or undefined
by accident, avoiding errors down the line.
function doSomething(value: string | null) {
if (value == null) {
// value is either null or undefined here.
console.log("No value provided.");
return;
}
// TypeScript knows value is a string here.
console.log(value.toUpperCase());
}
Nullish Coalescing:
Nullish coalescing is a way to handle null
or undefined
by providing a default value. It's like having a backup when something you expected to be there isn't.
const response = {
settings: null
};
const defaultSettings = { theme: "dark" };
const settings = response.settings ?? defaultSettings;
// settings will be the defaultSettings if response.settings is null or undefined.
These practices help ensure that you are handling the absence of values in a safe and predictable way, reducing bugs and making your code easier to understand.
Optional Chaining:
Optional chaining lets you read the value of a property without worrying if its parent is null
or undefined
. It's like tapping a domino that may or may not fall, but either way, you're safe.
const user = {
profile: {
name: 'Alice',
details: {
age: 25,
// ... other details
}
}
};
const userAge = user.profile?.details?.age;
// If profile or details are null/undefined, userAge will be undefined.
Type Assertions with null/undefined:
When you're sure that a value won't be null
or undefined
, you can use a type assertion to tell TypeScript to trust you.
function getButton(): HTMLButtonElement | null {
// ... some logic to get the button
}
const button = getButton();
if (button) {
// TypeScript knows button is not null here.
button.click();
}
Default Parameters:
Default parameters in functions provide a fallback value for arguments that are not provided or are undefined
.
function createGreeting(message: string, userName = "stranger") {
// userName will be "stranger" if not provided
return `${message}, ${userName}!`;
}
Using null
as a Clear Signal:
Choosing null
to represent an intentional absence of value makes your code's intention clear.
function clearUser(user: User | null) {
if (user === null) {
// Explicit check for 'null' indicates it's a deliberate absence of a user.
console.log("No user to clear.");
}
// ... logic to clear the user
}
Discriminated Unions with null/undefined:
Discriminated unions that include null
or undefined
force you to handle those cases, ensuring you don't forget to account for them.
type Response = { data: string } | null | undefined;
function handleResponse(response: Response) {
if (response === null) {
// Handle null
} else if (response === undefined) {
// Handle undefined
} else {
// Handle { data: string }
}
}
Avoiding null
and undefined
Pitfalls:
Learn the common issues that come up with null
and undefined
to avoid them in your own code.
Migrating with Strict Null Checks:
If you're moving an old project to use strict null checks, do it piece by piece to make sure you don't get overwhelmed.
Understanding Compiler Options:
Dive into the TypeScript compiler settings so you know how to make it work best for you, including how it handles null
and undefined
.
{
"compilerOptions": {
"strictNullChecks": true,
"strictPropertyInitialization": true
}
}
These tips give you the knowledge to write TypeScript code that's ready for the challenges of null
and undefined
, reducing bugs and making maintenance easier.