TypeScript Basics: Conditional Narrowing
Welcome to the Conditional Narrowing lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
At first glance, union types don't seem very useful. If we have a variable of type
User | User[], there's not much we can do with it. We can't treat it like a singleUserbecause it might be aUser[]. But we also can't treat it like aUser[]because it might be a singleUser.We get around this by handling both possibilities in our code. For example: if we're passed one user, we return their name. If we're passed an array of users, we return its length.
>
type User = {name: string};function nameOrLength(userOrUsers: User | User[]) {if (Array.isArray(userOrUsers)) {// Inside this side of the if, userOrUsers' type is User[].return userOrUsers.length;} else {// Inside this side of the if, userOrUsers' type is User.return userOrUsers.name;}}- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
nameOrLength({name: 'Amir'});Result:
'Amir'
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
nameOrLength([{name: 'Amir'}, {name: 'Betty'}]);Result:
2
userOrUsersis aUser | User[]. Why are we allowed to access itslengthand itsnamehere? Arrays don't have aname, andUsers don't have alength.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
const userOrUsers: User | User[] = [{name: 'Amir'}];userOrUsers.name;Result:
type error: Property 'name' does not exist on type 'User[]'.
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
const userOrUsers: User | User[] = {name: 'Amir'};userOrUsers.length;Result:
type error: Property 'length' does not exist on type 'User'.
The answer is that TypeScript can see our
if (Array.isArray(userOrUsers))check. IfArray.isArrayis true, then the type can only beUser[], notUser. The TypeScript compiler knows whatArray.isArraymeans when used in a conditional.The same logic applies to "else" side of the conditional. On that side,
Array.isArray(userOrUsers)is false, so it must be a singleUser, not aUser[].In both cases, we say that we've narrowed the type. We've taken a wide type like
User | User[]and reduced it to a narrower type likeUserorUser[].The
Array.isArrayfunction is a type guard. When used inside anifor other conditional, a type guard makes a guarantee about the type. It "guards" the code inside the conditional, ensuring that it only executes for certain types.