Suppose you want to do some Dependency Injection magic, or you just want to use the best practice to talk to interfaces instead of implementations.
For some reason language designers made constructs like `interface` and `class` and gave developers the idea that the construct being defined by the interface keyword is the actual type of interface we should program against.
Well lets humor me for a bit and deal with this type of pattern. We are gonna define interfaces and type aliases and classes for this exercise, but we're also gonna think about naming.
One thing I really don't like about naming conventions is deterring from the actual semantic meaning of a type. With this I mean all these kind of naming conventions like pre- or post-fixing interfaces with an `I`, or post-fixing concrete classes with `Impl`.
brrrr...
A few months ago I learned that you can have code like this:
export const RiskFactor = {
Low: 'low',
Medium: 'Medium',
High: 'High',
} as const;
export type RiskFactor = keyof typeof RiskFactor;
As you can see we have both a type alias and a const named as RiskFactor.
This is possible because types live in a different context as the const does.
In fact I rather like this I don't want to name the keys anything different than the const because I will use the same type everywhere interchangeably, so it's kinda bothersome to make the types have a different name.
Now comes the kicker:
when I program against an interface I actually want to use the type defined by the interface so and my class is of that type defined by the interface.
when I export a class `Foo` it will also export the type `Foo`. Remember that types and const live in different contexts so the classname `Foo` can both be used as a type and the identifier of the class construct like this:
export class Foo = {};
const bar: Foo = new Foo();
So when I thought of this, I thought that well this way I can actually use the real word to describe the type I want to use for both the interface (aka type definition) and the class:
export interface Foo {
name: string;
help(): string;
};
export class Foo implements Foo {}
To my surprise this works too well???
Why doesn't my code editor tell me to implement the missing properties?
to take this further:
...
const bar = new Foo()
bar.help() // this now fails with a runtime error not with a compiltime error!!!!
Was I doing something wrong? Cuz this fails immediately:
export interface IFoo {
name: string;
help(): string;
};
export class Foo implements IFoo {} // compile time error must implement missing properties name, help from IFoo
This fails as well:
export type Foo = {
name: string;
help(): string;
};
export class Foo implements Foo {}
This fails with the following warnings:
- Duplicate identifier 'Foo'. typescript (2300)
- Class 'Foo' incorrectly implements interface 'Foo'.
Type 'Foo' is missing the following properties from type 'Foo': name, help typescript (2420)
What I recon from this is that a class exports its definition as an interface and conflicts with type.
The reason I think why the duplicate name of the interface with class does work is due to the typescript feature of interface merging.
When you have 2 or more interfaces with the same name, yes this is allowed in typescript, it merges the definitions of these interfaces into one. I think classes and interfaces behave in the same way and that's why we don't have a compiletime error when using classes and interfaces with the same name.
But now the practical part.
This doesn't make any sense. I want the compile to direct me with helping me syncing the concrete implementation with the interface(s) it implements, but now typescript suddenly says to me: "Hey dude it's all up to you now, you're back in javascript-land except you aren't cuz interfaces aren't part of the javascript specifications. Well your problem not mine....."
I guess I still have to deal with types that don't mean anything or classes that have a suffix that is just noice.
No comments:
Post a Comment