
Typescript: class vs. interface - Parsyval
https://medium.com/front-end-hacking/typescript-class-vs-interface-99c0ae1c2136
======
dsiegel2275
"You’re saying that any object given this contract must be an object
containing 2 properties (no more, no less)"

The parenthetical 'no more, no less' should be removed here as it is
misleading. TypeScript interfaces do allow the objects that 'implement' them
to of course have other properties defined. See the LabelledValue example in
the TypeScript docs:

[https://www.typescriptlang.org/docs/handbook/interfaces.html](https://www.typescriptlang.org/docs/handbook/interfaces.html)

~~~
ThePadawan
Note that it's only misleading due to the ambiguous grammar:

The sentence can be read as "You’re saying that any object given this contract
must be an object containing 2 properties (the object may contain no more, no
less)"

or

"You’re saying that any object given this contract must be an object
containing 2 properties (you are saying no more, no less)".

------
Matthias247
My personal rule of thumb:

\- If I'm dealing with pure data (of varying sources) I use interfaces

\- If I'm modelling something which has an identity and state (and probably
attached methods to modify the state) I'm using a class.

Wrote a bit more about it here:
[https://stackoverflow.com/questions/40591413/interface-
and-c...](https://stackoverflow.com/questions/40591413/interface-and-class-in-
typescript/40595930#40595930)

~~~
electrotype
I agree. Using classes for entities allows you to use libraries such as
[https://github.com/typestack/class-
transformer](https://github.com/typestack/class-transformer)

And allows you to use "instanceof". User defined type guards for interfaces
are hackish at best.

~~~
Legogris
"instaceof" is something I would advise against for any code that might be
called from another module.

If you have different modules requiring that same module and they for whatever
reason (webpack behavior, node not deduping properly, npm link, different
versions, etc), that instanceof will fail and the caller will be left
scratching their head as to why their object fails the check.

In JavaScript (and by extension TypeScript), you are best left to duck-typing
for run-time checks.

------
azangru
> I know it can be confusing for people coming from an OOP language, but in
> Javascript an object IS NOT an instance of a class. I’ve worked with C++ for
> about ten years, so I understand it feels right when doing

> let mine = new MyClass();

> you get an ‘object’. But when thinking that you forget that Javascript is
> not a ‘class based’ language, it uses a prototypal approach.

I really wish he were more specific about this. When in Javascript we write:

    
    
      function Foo() {
        this.x = 'something';
      }
    
      foo = new Foo();
    
      foo instanceof Foo; // true
    
      foo.constructor === Foo; // true
    

the language tells us that the object `foo` is indeed an "instance" created by
the constructor function `Foo`. What is the essential difference between this
and what we have in object-oriented languages? What are the practical
implications of Javascript being "not a ‘class based’ language" that "uses a
prototypal approach"?

~~~
pluma
The class system is a bit hit and miss. In practice you will be fine if you're
using classes and you will be fine if you use prototypes but you should avoid
mixing them directly.

Strictly speaking, there is no essential difference because it's entirely
possible to implement classical inheritance using prototypal inheritance --
just not the other way around.

There's no classical equivalent of this, for example:

    
    
        foo = {hello: "world"};
        bar = Object.create(foo);
        console.log(bar.hello); // "world"
    

Note how bar has no "hello" property, so the property is looked up on its
prototype, foo.

Your code uses the "new" keyword. That keyword is pretty much emulating
classical behaviour on top of the prototypal inheritance. The following two
examples are equivalent:

    
    
        foo = new Foo(1, 2, 3);
    

and

    
    
        foo = Object.create(Foo.prototype);
        Foo.call(foo, 1, 2, 3);
    

Every function implicitly has a "prototype" property which is set to an object
with a "constructor" property set to the function itself.

Object.create creates a new object and sets its prototype (the internal
[[Prototype]] property, not the "prototype" property on the function) to its
argument.

There's a bit more going on underneath (mostly to allow certain optimisations)
but conceptually even the "new" keyword is just syntactic sugar.

~~~
pluma
To be clear: in practice there are three ways to use inheritance in JS:

1) Prototypes with constructors using the "new" keyword (ES3+):

    
    
        function Foo (name) {
          this.name = name;
        }
        Foo.prototype.hello = function () {
          return `Hello ${this.name}!`;
        };
        let foo = new Foo("world");
        console.log(foo.hello()); // Hello world!
    

2) Plain prototypes with Object.create (ES5+):

    
    
        let bar = {
          name: 'world',
          hello: function () {
            return `Hello ${this.name}!`;
          }
        };
        let baz = Object.create(bar);
        baz.name = 'Wisconsin';
        console.log(bar.hello()); // Hello world!
        console.log(baz.hello()); // Hello Wisconsin!
    

3) Classes (ES2015+):

    
    
        class Qux {
          constructor (name) {
            this.name = name;
          }
          hello () {
            return `Hello ${this.name}!`;
          }
        }
        let qux = new Qux("world");
        console.log(qux.hello()); // Hello world!
    

Note how all three examples do (roughly) the same thing but in slightly
different ways.

Inheritance in #1 is a bit awkward (which is why before ES2015 there were
myriads of different inheritance libraries).

In #2 it's fairly straightforward but type checks are unintuitive because
there really aren't any types involved (instanceof requires a constructor).

In #3 both inheritance and type checks are fairly straightforward but it also
uses more abstractions, which can make it more difficult to understand
conceptually (especially when coming from a language like Java or C# which is
syntactically similar).

IMO #1 is the worst of both worlds because the behaviour of the "new" keyword
and the special "prototype" property are difficult to understand and the
constructor looks like a regular function but expects to be called in a
special way (using the "new" keyword) and will break in unexpected ways if
called as a normal function:

> TypeError: Cannot set property 'name' of undefined

(or worse: outside strict mode it might not fail and actually try to write to
the global scope -- so remember to always "use strict")

While the value of a class is also a constructor function, calling it without
"new" will break with a distinct error that makes the mistake obvious and easy
to understand:

> TypeError: Class constructor Qux cannot be invoked without 'new'

~~~
azangru
Thanks :-)

I've never used Object.create for inheritance (although I heard it was a
favorite of Douglas Crockford's at some point). And the syntactic sugar of
es2015 classes is so appealing I never wrote constructor functions on a
regular basis.

What's so conceptually difficult about es2015 classes that confuses people?
They always felt intuitive to me. A bit restrictive perhaps, because you don't
have private methods, or instance properties, and if you want to call super in
a method you have to do it before anything else, but other than that — nothing
especially confusing.

------
zwieback
I don't understand the point at the end, showing that interface definitions
don't create code doesn't have anything to do with code generated for actual
instantiation. Once you create an instance of something, no matter what
mechanism is used, code will get created, contract enforcement has nothing to
do with that.

Comparing to OOP languages I see the class definitions in the beginning of the
post as "data grouping" and not as contracts so I don't quite understand
what's being contrasted here.

~~~
Parsyval
Of course, you are right when you say "Once you create an instance of
something [...] code will be created". But what I am pointing here is BEFORE
you create any instance of anything.

You define a contract, what your object should look like. With an interface
it's checked before runtime and then translates to no code. But using a class,
you have code generated for the runtime.

~~~
zwieback
Are you concerned about code size then? That would make sense for a browser
app, coming from desktop I'm not too concerned with code that gets generated
but might not get called.

------
pluma
The comparison of the JS output is not entirely fair considering it's likely
trying to generate ES5 compatible JavaScript and thus "emulating" classes
rather than using the built-in classes available in ES2015 and later.

The output of Babel (which translates newer language features to older
versions like ES5) is actually even more verbose because it adds some runtime
error handling:

[https://babeljs.io/repl/#?babili=false&browsers=&build=&buil...](https://babeljs.io/repl/#?babili=false&browsers=&build=&builtIns=false&code_lz=MYGwhgzhAEAqCmEAu0DeAoAvu9pIwWQCZp4APJeAOwBMDEUNMg&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&lineWrap=true&presets=es2015%2Creact%2Cstage-2&prettier=false&targets=&version=6.26.0&envVersion=)

JavaScript classes build on prototypes with constructors and use prototypal
inheritance under the hood, but they're more than the sum of their parts and
can be a useful abstraction.

I'd argue that the article is right that in most cases it's probably cleaner
in TypeScript to simply use an interface or type alias instead of a class, but
although some people like (for example) Eric Elliott have very strong feelings
to the contrary, classes are a useful language feature for certain scenarios
and avoiding them at all costs can result in worse code than knowing when to
use them.

------
leshow
If you want to be purely structural, why even use an interface? You can just
as easily write:

type MyType = { a: number, b: string };

~~~
eridius
That's basically the same thing as using an interface. An interface is just a
more formal way of declaring it.

~~~
leshow
The difference IMO is that the interface is still a nominal declaration. With
it you have a named type, while the alias is purely structural. in TS/Flow I
prefer to use the type alias ubiquitously, however in other typed languages I
prefer being more nominal. In Purescript, which supports both methods also, I
almost always fully declare the type.

~~~
eridius
TypeScript interfaces are purely structural. The effect on the type system of
using `interface Foo { … }` vs `type Foo = { … }` is the same. I mean, the
whole point of interfaces in TypeScript is that it's structural typing, and if
you want nominal typing you use a class.

The only difference is readability and how tools like VSCode treat it (e.g. if
you hover over something typed as an `interface`, VSCode just shows the
interface name, though you can hold a modifier to see the whole definition).

All that said, if you want to exclusively use type aliases, you're certainly
free to do so. But I expect most TypeScript developers will consider that to
be a bit odd.

------
the_gipsy
I used to think that classes get special treatment in typescript, because they
both reference a type and a constructor function. But I recently found out
that you can do the same by exporting a constructor function and it's type
with the same name:

module a:

    
    
        export type A = { a: string };
        export const A : () => A = () => { return {a: string}; };
    

module b:

    
    
        import A from './a';
        const a = A(); // a is of type A
    

This way you can completely ditch classes, and still only import a single
type/constructor combo. Not having to use `new` means easier composition and
generally going more in the direction of functional programming.

~~~
TomMarius
This is in no way equal to having a 'class A { ... }' that you'd instantiate
with 'new A()'. In module b, the const a will not be of 'type A', but a plain
object with signature of '{ a: string }' for which A is an alias. That's very
different because a plain object _is not_ an instance - you can't use
instanceof, try to console.log() it and an instance of a class and you'll see
the difference.

~~~
the_gipsy
I didn't mean that it's equal to having `class A {...}`. What I said is that
you can export a type _and_ a function with the same name, which is as
convenient as importing a class, which is also both a type and a constructor
function (edit: with prototype).

By "constructor function" I meant a function that constructs an object with a
given shape, not a "class instance" or prototyped object. Sorry about not
being clear. So no `instanceof`, but you are guaranteed to get correct type
checking.

See here [1] for example, if you hover `a` it will tell you it is an `A` not a
`{a:string}`.

[1]:
[https://www.typescriptlang.org/play/#src=type%20MapToProps%3...](https://www.typescriptlang.org/play/#src=type%20MapToProps%3CT%3E%20=%20\(\)%20=%3E%20T;%0D%0Atype%20Component%3CT%3E%20=%20\(props:%20T\)%20=%3E%20any;%0D%0Adeclare%20const%20connect:%20%3CT,%20U%3E\(%0D%0A%20%20a:%20MapToProps%3CT%3E,%0D%0A%20%20b:%20MapToProps%3CU%3E%0D%0A\)%20=%3E%20\(c:%20Component%3CT%20&%20U%3E\)%20=%3E%20any;;%0D%0A%0D%0Aconst%20A%20=%20\(\)%20=%3E%20\({%20a:%201%20}\);%0D%0Aconst%20B%20=%20\(\)%20=%3E%20\({%0D%0A%20%20%20%20x:%201,%0D%0A%20%20%20%20//%20b:%203,%0D%0A}\);%0D%0A%0D%0Atype%20I%20=%20{%20a:%20number,%20b:%20number%20};%0D%0A%0D%0Aconst%20MyComponent%20=%20\(props:%20I\)%20=%3E%20'MyJsx';%0D%0A%0D%0Aconnect\(A,%20B\)\(MyComponent\);%0D%0A)

~~~
TomMarius
Yes, Typescript will tell you that it's of type A, but that's not what happens
at runtime - that's what I wanted to say. During runtime it's a plain object
that has the aforementioned signature.

------
ggregoire
> One can agree or disagree, but like he says : “classes are nice but they are
> not necessary in Javascript”. I would say, since they are here and make life
> easier for a lot of people, you can use them for whatever reason you want

How unnecessary classes make life easier for a lot of people? It's probably
one of the worst JS anti-patterns.

------
kbumsik
I am new to Typescript, but I don't quite see why and how it hurts at the end.
He defines two interfaces that are not implemented in any classes, so those
interfaces don't do anything to the actual program yet. I guess it is safe for
Typescript to produce no code. Any specific scenario this will be problematic?

~~~
slig
> I guess it is safe for Typescript to produce no code.

TypeScript produces no code in that case because interfaces are a TypeScript
feature and are only used to check your code before compiling it down to
JavaScript.

------
d76d6776yudsy
OP lost me quickly when ranting `in Javascript an object IS NOT an instance of
a class.` while pointing at some TypeScript.

Also, plain javascript has had proper classes since the ECMAScript 2015
standard.

~~~
Parsyval
'proper classes' no 'class' keyword syntaxical sugar, behind it's all
prototypal. Please understand that if you are doing JS.

------
the_gipsy
So, basically, creating classes generates JS, interfaces/types aliases don't.

Don't create classes only for the types, that's what interfaces and type
aliases are good for.

