7

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?

Paleo
  • 21,831
  • 4
  • 65
  • 76
user1283776
  • 19,640
  • 49
  • 136
  • 276
  • 1
    Possible duplicate of [Typescript: Interfaces vs Types](https://stackoverflow.com/questions/37233735/typescript-interfaces-vs-types) – Karol Majewski Feb 05 '19 at 14:42
  • @KarolMajewski Yes and unfortunately the [best answer](https://stackoverflow.com/a/52682220/3786294) is not the accepted answer. – Paleo Feb 05 '19 at 14:54
  • @Paleo: That post is very good I think, but it only talks about minor technical differences between interfaces and type aliases. It doesn't talk at all about semantic differences – user1283776 Feb 05 '19 at 14:57

2 Answers2

3

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.

About interface inheritance and type intersection in TypeScript

In 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.

Paleo
  • 21,831
  • 4
  • 65
  • 76
  • Thank you! If I may ask a related follow-up questions: do you think there is a need for both interfaces and types in TypeScript or do you think that only one of those would be included if TypeScript was redesigned from the ground up? – user1283776 Feb 06 '19 at 07:44
  • 1
    @user1283776 I'm unsure. But IMO the current syntax is fine. There are several ways to do the same thing, it is like JavaScript. We can say interfaces are "curly braced types": then the syntax `type someType = { … }` is an anonymous interface assigned to a type, the same way a function is a named constant but `const myFn = function () { … }` is an anonymous function assigned to a constant. – Paleo Feb 06 '19 at 08:28
2

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.

Robbie Milejczak
  • 5,664
  • 3
  • 32
  • 65
  • A `type` can also extend a `class`: `type MyType = MyClass & { otherProp: string; }`. It seems the declaration merging is the only difference? – Paleo Feb 05 '19 at 14:44
  • Another difference: a `type` can be composed with `|` and can be an alias for primitive types. – Paleo Feb 05 '19 at 14:48
  • Those seem like minor technical differences to me, not semantic differences that can help me choose the right tool for the situation – user1283776 Feb 05 '19 at 14:55
  • @Paleo that is not extending, that is an intersection type. Although I had meant to say interfaces can *implement* classes, which I corrected in my post – Robbie Milejczak Feb 05 '19 at 15:06
  • @user1283776 yes the differences are minor, but the largest semantic difference is that interfaces can implement classes, extend other interfaces, and merge with themselves. There is a tslint rule called `interface-over-type-literal` and the folks over at palantir provide this rationale: "Interfaces are generally preferred over type literals because interfaces can be implemented, extended and merged." – Robbie Milejczak Feb 05 '19 at 15:08
  • @user1283776 When an interface extends another interface, there is no real semantic relation parent/child. It is just like with the first interface `&` the new members. – Paleo Feb 05 '19 at 15:13
  • see my edit, there are differences in intersections and implementations that could affect type safety in your application – Robbie Milejczak Feb 05 '19 at 15:14
  • @RobbieMilejczak But you can do: `interface C extends A { num: string & number; }` and you obtain exactly the same result as with a type intersection. (EDIT: and no the type intersection doesn't _"overwrites the num property type"_. It is still an intersection.) – Paleo Feb 05 '19 at 15:16
  • just because you can use syntax to obtain the same result doesn't make it the same, and `string & number` is an awful result that you would never want. Interfaces provide a defense against this, type intersections do not – Robbie Milejczak Feb 05 '19 at 15:19
  • I'm not sure what you think you're saying but it doesn't make a lot of sense. You do not want to have a type with `num: string & number`, it is unusable. If you use types and intersections, you could end up with this type without knowing it. The error that the interface provides is desirable, and the behavior of intersections vs extending an interface are clearly disparate. An intersection and an extension are not the same thing and if you use them interchangeably you'll end up with weird types like `number & string` – Robbie Milejczak Feb 05 '19 at 15:25
  • @RobbieMilejczak: Can you explain the semantic difference between interfaces and types in a way that is not purely technical? Like someone could explain the difference between classes and objects as classes being templates for objects and objects being instances of classes. I don't understand the implications of "largest semantic difference is that interfaces can implement classes, extend other interfaces, and merge with themselves" for 90+% of my use cases. I am still looking for any possible semantic guidance on whether to use interfaces or type aliases in those cases. – user1283776 Feb 06 '19 at 09:29
  • In 90% of use cases you can use both, type aliases have a few gotchas when working with intersection and unions and also trying to extend them or like the example in my post but other than that they are almost identical in behavior for the majority of use cases. I would just pick whichever one you prefer stylistically – Robbie Milejczak Feb 06 '19 at 13:47