null and undefined
Sometimes a value is missing: a hero has no pet, a chest is empty, a search finds nothing. JavaScript has two values for “nothing”. undefined is what you get from a missing property or argument. null is what programmers store on purpose, to say “there's nothing here”. You met null in the JavaScript course: querySelector() returned it when it found no element, and the next line crashed with TypeError: Cannot read properties of null. That crash is one of the most common bugs in JavaScript programs.
In TypeScript, null and undefined have types of their own, and other types don't include them. A string variable can't hold null:
let target: string = null;main.ts(1,5): error TS2322: Type 'null' is not assignable to type 'string'.When a value may be missing, you say so with a union: Pet | null is “a pet, or nothing”. And then TypeScript makes sure that you check the value before you use it:
type Pet = { name: string; species: string };
type Hero = { name: string; pet: Pet | null };
function describePet(hero: Hero): string {
return `${hero.name} has a pet ${hero.pet.species} called ${hero.pet.name}`;
}main.ts(5,38): error TS18047: 'hero.pet' is possibly 'null'.
main.ts(5,65): error TS18047: 'hero.pet' is possibly 'null'.“Possibly null” means: when the function runs, hero.pet may be null, and then reading hero.pet.species would crash with TypeError: Cannot read properties of null (reading 'species'). In JavaScript, you'd find out when the program crashes, and only if somebody calls the function for a hero without a pet. TypeScript tells you before the program runs.
The fix is a check, and it narrows the type just like typeof did:
type Pet = { name: string; species: string };
type Hero = { name: string; pet: Pet | null };
function describePet(hero: Hero): string {
if (hero.pet === null) {
return `${hero.name} has no pet`;
}
return `${hero.name} has a pet ${hero.pet.species} called ${hero.pet.name}`;
}
const kira: Hero = { name: "Kira", pet: { name: "Pip", species: "owl" } };
const bruno: Hero = { name: "Bruno", pet: null };
console.log(describePet(kira));
console.log(describePet(bruno));This prints:
Kira has a pet owl called Pip
Bruno has no petBelow the if with return, hero.pet is a Pet. You could also check it the other way round, with if (hero.pet !== null) { … }; then hero.pet is a Pet inside the braces. As you can see, narrowing works on properties too, not only on variables and parameters.
pet: Pet | null isn't the same as an optional property pet?: Pet. Here, the property must always be there, and a hero without a pet has pet: null. An optional property may be left out, and its type is Pet | undefined: the error then says is possibly 'undefined' (TS18048), and you check it with !== undefined.
Typical mistake: checking for the wrong “nothing”. if (hero.pet !== undefined) doesn't rule out null, so for a Pet | null, TypeScript still reports 'hero.pet' is possibly 'null'. Check for the value that the type contains.
Instructions
Run the program and fix the errors with a check: when target is null, the function prints There is nothing to attack and does nothing else. The program then prints:
The wolf has 18 health left
There is nothing to attackKira hasn't chosen a new target yet. Below the calls, create a variable (let) currentTarget of the type Monster | null with the value null, and call attack(currentTarget, 5).
Then Kira spots a goblin. Store { name: "goblin", health: 20 } in currentTarget, and call attack(currentTarget, 5) again. The program then prints:
The wolf has 18 health left
There is nothing to attack
There is nothing to attack
The goblin has 15 health leftStart programming for free
4/10