TypeScript Basics: Object Narrowing
Welcome to the Object Narrowing lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
We can assign object types to "smaller" object types. This is called narrowing: we narrow the "larger" type to the "smaller" type. For example, we might have a full user type:
>
type User = {email: stringadmin: boolean};let amir: User = {email: 'amir@example.com',admin: true,};- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
amir.email;Result:
'amir@example.com'
But sometimes we might not care about the whole user. Maybe sometimes we only care about the email address.
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
type HasEmail = {email: string};let amirEmail: HasEmail = amir; - Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
amirEmail.email;Result:
'amir@example.com'
When we do this, the 'smaller' type loses knowledge of where it came from. For example, our
HasEmailtype doesn't know about theadminproperty.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
amirEmail.admin;Result:
type error: Property 'admin' does not exist on type 'HasEmail'.
Notice that in the example above, we didn't say that
UserandHasEmailare related. TypeScript automatically knows that they're related becauseHasEmailis a "smaller" version ofUser.Userhas an email and an admin flag, butHasEmailonly has an email.This is called structural typing: the object's structure determines the type. Some other programming languages would require us to explicitly say "a
Useris a bigger kind ofHasEmail." That would be nominal typing and is not supported by TypeScript.Narrowing can be very useful. For example, our system might have a
sendEmailmethod. If it takes aUser, then we'll need a full user to call it.However, a
sendEmailthat takes a{email: string}is more flexible. We can pass it a user, or we can pass it just an{email: string}to send an email to an address not associated with a user.>
type User = {email: string, admin: boolean};let amir: User = {email: 'amir@example.com', admin: true};function sendEmail({email}: {email: string}): string {return `Emailing ${email}`;}[sendEmail(amir),sendEmail({email: 'betty@example.com'}),];Result:
['Emailing amir@example.com', 'Emailing betty@example.com']
Finally, a note on terminology. Structural typing is also sometimes called "duck typing". This term is a reference to a saying: "If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck."
In programming, "duck typing" means that we only care about the properties we're accessing. We don't care whether the type's name is
UserorHasEmail, and we don't care whether the object has extra properties.