TypeScript has the capability of extending and merging interfaces.
Our development will be more efficient by utilizing these advanced features.
Basic Usage of Interfaces
Interfaces are similar to object-type aliases. Here is a comparison between an interface and an aliased object type.
type Vehicle = {
wheels: number;
maker: string;
};
interface Vehicle {
wheels: number;
maker: string;
}
This type and interface can be used in almost the same way.
In this article, I will dig deeper into the interface to introduce more advanced techniques.
Interface Extensions
You may notice that some interfaces sometimes look similar to each other when it comes to a big project. If some interfaces have common properties, it is a good time to extend one interface and make another one. Let me show an example:
interface Vehicle {
wheels: number;
maker?: string;
}
interface Car extends Vehicle {
power: "gas" | "electricity";
}
interface Bicycle extends Vehicle {
folding: boolean;
}
const myCar: Car = {
wheels: 4,
maker: "Toyota",
power: "gas"
} // OK
const car: Car = {
wheels: 4,
maker: "Honda",
power: "gas",
folding: false
} // Error
// Type '{ wheels: number; maker: string; power: "gas"; folding: boolean; }' is not assignable to type 'Car'.
// Object literal may only specify known properties, and 'folding' does not exist in type 'Car'.
const bicycle: Bicycle = {
wheels: 2,
} // Error
// Property 'folding' is missing in type '{ wheels: number; }' but required in type 'Bicycle'.
Both Car and Bicycle interfaces have common properties, wheels (required) and a maker (optional). Therefore, they can derive these properties from the Vehicle interface.
Cool! You can avoid repetitive code by using this technique.
What If You Declare the Same Property in a Child Interface?
It is possible to declare the same name of the property again when making a descendant interface. However, it does not mean you can freely override the parent’s property. The rule is you can override a property as long as a new re-declared property is assignable to the parent one.
Here is an example:
interface Vehicle {
wheels: number;
maker?: string;
}
interface Car extends Vehicle {
power: "gas" | "electricity";
}
interface ElectricCar extends Car {
power: "electricity";
} // OK
interface FuturisticCar extends Car {
power: "air";
} // Error
// Interface 'FuturisticCar' incorrectly extends interface 'Car'.
// Types of property 'power' are incompatible.
// Type '"air"' is not assignable to type '"gas" | "electricity"'.
As you can see above, "electricity"
is assignable to the literal union type, "gas" | "electricity"
, because "electricity"
is included in the union type. Thus, it’s possible to make ElectricCar
by extending the Car
interface.
On the other hand, the TypeScript type checker yells at you when making the FuturisticCar
interface because "air"
is not included in "gas" | "electricity”
.
Interface Merging
In the previous section, I explained what happens when you declare the same property while extending another interface.
Next, guess what happens when you declare multiple interfaces with the same name.
interface Vehicle {
wheels: number;
maker?: string;
}
interface Vehicle {
price: number;
}
const vehicle: Vehicle = {
wheels: 4,
maker: "Nissan",
price: 25000
} // OK
const vehicle2: Vehicle = {
maker: "Nissan",
price: 25000
} // Error
// Property 'wheels' is missing in type '{ maker: string; price: number; }' but required in type 'Vehicle'.
const vehicle3: Vehicle = {
wheels: 4,
maker: "Nissan",
} // Error
// Property 'price' is missing in type '{ wheels: number; maker: string; }' but required in type 'Vehicle'.
In this example, Vehicle
is declared twice. Since both interfaces have the same name, TypeScript merges them into one interface. As a result, the final Vehicle
interface has wheels
, maker
, and price
properties. Technically, it is the same as declaring one Vehicle
interface with the three properties.
But what if those interfaces have the same property name with different types?
Let’s take a look at the example code below:
interface Vehicle {
wheels: number;
maker?: string;
plate: number;
}
interface Vehicle {
plate: string;
} // Error
// Subsequent property declarations must have the same type. Property 'plate' must be of type 'number', but here has type 'string'.
It is not allowed to declare the same property with different types because it causes conflicts. The TypeScript type checker will warn that subsequent property declarations must have the same type.
Conclusion
- Interface extensions allow developers to avoid repetitive code by deriving common properties from a parent interface.
- When declaring the same property in a child interface, it is only allowed if the new re-declared property is assignable to the parent one.
- Interface merging can be used to combine interfaces with the same name, but it is not allowed to declare the same property with different types as it causes conflicts.
Top comments (0)