1

This question is not a dupe of this one, despite the similarites in the title. I'm not asking about the differences but the implications of the differences. Also, I've considered the risk of being opinion-based and voided it by limiting to two viable options and specifying a set of conditions.

I asked a question and got a type based answer, which worked.

export type Configs = {
  [key: string]: ButtonConfig | TextBoxConfig | CheckConfig;
}

When I applied that in my project, the IDE TSLint'ed a sugestion to apply interface based approach instead.

export interface Configs {
  [key: string]: TextBoxConfig | ButtonConfig | CheckConfig;
}

Those are two fundamentally different approaches and I got curious as to which would be most appropriate and when, according to best practices. A set of blogs like this one offer comparison tables. However, I'm confused as to the implication of said differences. For instance: "allows the creation of the new name for a type" is hard to evaluare versus "provides the powerful way to define entities", from my point of view. It's like saying "this one is large" and "that one has bananas".

I've found blogs that contradict and correct other blogs and docs, too. There are questions on SO too but those I've found, list the deviations rather than elaborating on the implications thereof. All in all, it's unclear to me how to interpret the info base.

The original poster, @thorjacobsen, replied that in his team they went for type based because of semantical preference, which is a local reason, not guaranteed to be applicable in a general case.

Of what I gathered, I get a sense that (unless other requirements impose a choice), interface is preferred by teams coming from .NET (keywork familiarity) while type is preferred by team with JS experience (historial sentiment). NB, I'm speculating here.

Given that times change and the methodics adapts to the wind of time, I wonder which one (if any) of the two would be suggested in a general case as the best practice (or safest bet until more info emerges).

DonkeyBanana
  • 3,266
  • 4
  • 26
  • 65
  • 1
    I'm not exactly sure what answer you are looking for which the "not dupe" question doesn't already provide. As it states, in 2019 the only non-semantic difference `interface` has over `type` is declaration merging. – lukasgeiter Sep 06 '19 at 08:31
  • @lukasgeiter I might be mistaken, in which case I'll gladly stand corrected on the nondupiness claim. What confuses me in the information I've gathered is that there are two, virtually equivalent approaches to achieve the same kind of goal. It seems that it's (almost) a matter of taste and computer languages are not created that way. We pick the most optimal approach and make it default, if possible. In other words, there's no point introducing feature x and there's already feature y that's doing the same thing. – DonkeyBanana Sep 07 '19 at 11:08
  • @lukasgeiter Sometimes there's need for that for compatibility issues (*as* and **) but generally, it implies that the reader (i.e. me) is mistaken so badly that they can't see it. So I assumed that there's a way to make a judgement call on interface versus type and that I'm simply to dense to see it. – DonkeyBanana Sep 07 '19 at 11:09

2 Answers2

4

First, the facts: this answer and this one with examples are absolutely correct.

The only thing interfaces offer over types is declaration merging. On the other side, types can do a lot more than interfaces (mapped types, conditional types, etc).


But why do we have both?

Now, you understandably wonder why the language was designed that way:

What confuses me in the information I've gathered is that there are two, virtually equivalent approaches to achieve the same kind of goal. It seems that it's (almost) a matter of taste and computer languages are not created that way. We pick the most optimal approach and make it default, if possible. In other words, there's no point introducing feature x and there's already feature y that's doing the same thing.

I'll try to answer this as well as I can by giving some background on the history of TypeScript.

From the very beginning TypeScript has had interfaces as a way to define the named type of an object or function:

interface Foo {
    bar: number;
}

However, in some situations you didn't necessarily have to specify an interface for TypeScript to understand the structure of an object. Take this example:

let foo = {
    bar: 42
}

The compiler will infer that foo has the type { bar: number }. It's even possible to manually annotate the type without creating an interface:

let foo: { bar: number } = {
    bar: 42
}

This shows that it was always possible to create complex types without interfaces. There was just no way to put a name to them as to make them more readable and re-usable.

Then TypeScript 1.4 introduced type aliases. The new type keyword allows to define an alternative name for any kind of type. They work for interfaces:

MyFoo = Foo

But also for types which previously couldn't be defined using a name:

type MyNumberArray = number[];
type MyFunction = (arg: string) => void;

Of course this now meant we can also define our interface Foo only using type:

type Foo = {
    bar: number;
};

Since TypeScript 1.6, type aliases could be generic as well allowing them to describe all possible type constructs known to TypeScript.

To summarize: Types are designed to be type aliases, a way of giving any type a name. This includes any type that an interface can represent.


Which one should I use?

Which one to use is a matter of personal taste. There is no right or wrong, except that it's important to be consistent. The following are a few options how one could decide to go about this (I'm ignoring declaration merging here because in most projects it's not used)

  • Always use types
    One syntax for everything
  • Always use interfaces unless types are needed
    For devs who are more comfortable with the syntax and semantics of interfaces
  • Interfaces for classes, types for everything else
    For devs who are more comfortable with the semantics of classes and interfaces

Of course there are many more ways to do it, and you'll have to find something that suits your needs.

lukasgeiter
  • 147,337
  • 26
  • 332
  • 270
  • This is precisely, exactly and perfectly what I was looking for in a comprehensive form. I can clearly see how that information was available at the other sources I've been at before you wrote this answer. However, that information was not available **to me** due to mix of ignorance, confusion and insecurity. Thank you very much for the effort - it's a kick-ass answer. – DonkeyBanana Sep 07 '19 at 14:40
1

Different people can have different approaches. One possible and very simple approach:

  • Use type aliases to describe the exact shape of a custom piece of data.

  • Use interfaces to describe the exact shape of a custom piece of functionality related to class or function signature.

This is not a line drawn in sand and the distinction between the two can easily get blurred. For example, because type alias is an alias, it can be used to alias nearly everything including a piece of functionality (like a function) providing another name e.g. alias for a specialized functionality and so forth. Still, having some simple initial distinction is helpful in my view.

winwiz1
  • 2,906
  • 11
  • 24