Typescript: class vs. interface
medium.com
medium.com
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
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)".
- 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...
And allows you to use "instanceof". User defined type guards for interfaces are hackish at best.
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.
> 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"?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.
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'
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.
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.
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.
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...
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.
type MyType = { a: number, b: string };
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.
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.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...
How unnecessary classes make life easier for a lot of people? It's probably one of the worst JS anti-patterns.
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.
Also, plain javascript has had proper classes since the ECMAScript 2015 standard.
Don't create classes only for the types, that's what interfaces and type aliases are good for.