I can do most of the same thing with interfaces and type aliases.
For example
Classes can implement either interfaces or type aliases
interface Shape {
area(): number;
}
type Perimeter = {
perimeter(): number;
}
class Rectangle implements Shape, Perimeter {
}
They can be combined to create new interfaces/type aliases
interface A {
a: string;
}
type B = {
b: string
}
interface C extends B {
c: string;
}
type D = A & {
d: string;
}
Is there a semantic difference between the interfaces and type annotations?
There are technical differences between interface
and type
that are well-described here.
However, for the cases where a type
and an interface
can both be used, there is no semantic difference at all.
interface
inheritance and type
intersection in TypeScriptIn TypeScript, the hierarchy between interfaces is just a way to define interfaces. But once they are defined, there is no real parent-child relationships between interfaces. For example:
interface Named {
name: string
}
interface Person extends Named {
age: number
}
interface Animal {
name: string
age: number
}
Here Person
and Animal
are the same type. Once they are defined, they will be processed exactly the same way by the compiler when other code use them:
function useNamed(named: Named) {
}
let p: Person = /* ... */
let a: Animal = /* ... */
useNamed(p) // OK
useNamed(a) // also OK, because if 'Animal' is compatible with
// `Named` then it is a `Named`
That's why the same type can also be created using an intersection type:
type Engine = Named & {
age: number
}
From the specification:
Intersection types represent values that simultaneously have multiple types. A value of an intersection type A & B is a value that is both of type A and type B. (source: TypeScript Specification)
Our Engine
type is both a Named
and the additional definition: it is semantically the same thing as interface inheritance. And Engine
type here is exactly the same type as Person
and Animal
.
Interfaces can extend other interfaces and can also *implement classes. Interfaces can also take advantage of declaration merging:
type A = {
a: string;
}
type A = { // <-- error: duplicate identifier A
b: string;
}
interface A {
a: string;
}
interface A { // <-- okay, A is { a: string, b: string }
b: string;
}
edit: changed extend to *implement
edit 2: intersections are not the same as extending. Consider the following:
interface A {
num: number;
str: string;
}
type B = A & { // <-- this is okay, but overwrites the num property type to number & string
arr: any[];
num: string;
}
interface C extends A { // <-- error: C incorrectly extends A
num: string;
}
edit 3: another potentially significant difference for some people (though not necessarily a semantic difference) is that types are enumerated in tool tips (at least in vscode) but interfaces are not.
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