Let's say the following type is defined:
interface Shape {
color: string;
}
Now, consider the following ways to add additional properties to this type:
Extension
interface Square extends Shape {
sideLength: number;
}
Intersection
type Square = Shape & {
sideLength: number;
}
What is the difference between both approaches?
And, for sake of completeness and out of curiosity, are there other ways to yield comparable results?
An intersection type is a type that merges several kinds into one. This allows you to combine many types to create a single type with all of the properties that you require. An object of this type will have members from all of the types given. The '&' operator is used to create the intersection type.
TypeScript lets you augment an interface by simply declaring an interface with an identical name and new members. This lets you extend existing JavaScript code without creating a new named type.
// One major difference between type aliases vs interfaces are that interfaces are open and type aliases are closed. This means you can extend an interface by declaring it a second time. // In the other case a type cannot be changed outside of its declaration.
An interface can be extended by other interfaces. In other words, an interface can inherit from other interface. Typescript allows an interface to inherit from multiple interfaces. Use the extends keyword to implement inheritance among interfaces.
Yes there are differences which may or may not be relevant in your scenario.
Perhaps the most significant is the difference in how members with the same property key are handled when present in both types.
Consider:
interface NumberToStringConverter {
convert: (value: number) => string;
}
interface BidirectionalStringNumberConverter extends NumberToStringConverter {
convert: (value: string) => number;
}
The extends
above results in an error because the derriving interface declares a property with the same key as one in the derived interface but with an incompatible signature.
error TS2430: Interface 'BidirectionalStringNumberConverter' incorrectly extends interface 'NumberToStringConverter'.
Types of property 'convert' are incompatible.
Type '(value: string) => number' is not assignable to type '(value: number) => string'.
Types of parameters 'value' and 'value' are incompatible.
Type 'number' is not assignable to type 'string'.
However, if we employ intersection types
type NumberToStringConverter = {
convert: (value: number) => string;
}
type BidirectionalStringNumberConverter = NumberToStringConverter & {
convert: (value: string) => number;
}
There is no error whatsoever and further given
// And this is a good thing indeed as a value conforming to the type is easily conceived
const converter: BidirectionalStringNumberConverter = {
convert: (value: string | number) => {
return (typeof value === 'string' ? Number(value) : String(value)) as string & number; // type assertion is an unfortunately necessary hack.
}
}
const s: string = converter.convert(0); // `convert`'s call signature comes from `NumberToStringConverter`
const n: number = converter.convert('a'); // `convert`'s call signature comes from `BidirectionalStringNumberConverter`
Playground Link
This leads to another interesting difference, interface
declarations are open ended. New members can be added anywhere because multiple interface
declarations with same name in the same declaration space are merged.
Here is a common use for merging behavior
lib.d.ts
interface Array<T> {
// map, filter, etc.
}
array-flat-map-polyfill.ts
interface Array<T> {
flatMap<R>(f: (x: T) => R[]): R[];
}
if (typeof Array.prototype.flatMap !== 'function') {
Array.prototype.flatMap = function (f) {
// Implementation simplified for exposition.
return this.map(f).reduce((xs, ys) => [...xs, ...ys], []);
}
}
Notice how no extends
clause is present, although specified in separate files the interfaces are both in the global scope and are merged by name into a single logical interface declaration that has both sets of members. (the same can be done for module scoped declarations with slightly different syntax)
By contrast, intersection types, as stored in a type
declaration, are closed, not subject to merging.
There are many, many differences. You can read more about both constructs in the TypeScript Handbook. The Interfaces and Advanced Types section are particularly relevant.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With