Everyday TypeScript: Extending Types
Welcome to the Extending Types lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
An earlier lesson showed what happens when two interface declarations have the same name. We get "declaration merging": the final interface has all of the properties from both interface declarations.
Declaration merging is useful, but has an important gotcha. The problem is that it overwrites the original interface, leaving us no way to use both interfaces.
More often, we want to say "this new interface is like the original, but has some additional properties". We want to be able to use both interfaces in different situations: the original "smaller" interface, and the new "larger" interface that extends it.
Fortunately, we can do exactly that with an interface that
extendsanother interface. The extending interface automatically has all of the properties from the extended interface.>
interface CanBeAdmin {admin: boolean}interface User extends CanBeAdmin {name: string}const user: User = {name: 'Amir', admin: true};user;Result:
{name: 'Amir', admin: true}Interfaces can also extend object types defined using the
typekeyword.>
type CanBeLocked = {locked: boolean};interface User extends CanBeLocked {name: string}const user: User = {name: 'Amir', locked: false};user;Result:
{name: 'Amir', locked: false}Interfaces can even extend classes. That may seem strange at first: classes can have methods and can be instantiated. Interfaces can't do either of those, so what does it mean for an interface to extend a class?
When an interface extends a class, it means "objects with this interface type must have the properties that an instance of the class would have." For example, if an interface extends a class that has a
canWalk(): booleanmethod, then an object with that interface must have acanWalk: () => booleanproperty. The same applies to the class's fields: an interface that extends the class must have each of the class's fields.>
class Animal {legs: number;constructor(legs: number) {this.legs = legs;}canWalk() {return this.legs > 0;}}// Pets are animals, but they also have names.interface Pet extends Animal {name: string}const msFluff: Pet = {legs: 4,canWalk: () => true,name: 'Ms. Fluff',};- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
[msFluff.name, msFluff.canWalk()];Result:
['Ms. Fluff', true]
It's important to note that Ms. Fluff is not an instance of
Animal! She has the same interface asAnimal, meaning that she has the same properties. ButmsFluffis aPet, which isn't an instance of any class at all.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
msFluff instanceof Animal;Result:
false
Our
Pettype is a combination of the originalAnimalclass's fields and methods, plus the extranameproperty. But it's enforced as if all of its properties were declared in a single interface declaration. If we omit any of the original class's properties, including its methods, then we'll get a type error.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
interface Pet extends Animal {name: string}const msFluffMissingLegs: Pet = {canWalk: () => true,name: 'Ms. Fluff',};Result:
type error: Property 'legs' is missing in type '{ canWalk: () => true; name: string; }' but required in type 'Pet'. Here's a code problem:
We have separate
CatandCanBeVaccinatedinterfaces, but they're not currently related. Modify theCatinterface to extend theCanBeVaccinatedinterface, which will give it all ofCanBeVaccinated's properties. (You won't need to modify the interface's properties.)interface CanBeVaccinated {vaccinated: boolean}interface Cat extends CanBeVaccinated {name: string}const msFluff: Cat = {name: 'Ms. Fluff', vaccinated: true};msFluff;- Goal:
{name: 'Ms. Fluff', vaccinated: true}- Yours:
{name: 'Ms. Fluff', vaccinated: true}
Finally, a note about where we can use
extends. In a previous lesson, we saw that classes can extend classes. In this lesson, we saw that interfaces can extend classes, object types, and other interfaces.However, types defined with the
typekeyword can't extend anything. There's simply no syntax for it: we can't saytype Cat extends Animal = { ... }.With
type, we can get a similar effect by intersecting types using the&operator. Unfortunately, that produces worse type error messages, which we'll see in a future lesson.