I'm learning Typescript, and on the Typescript cheat sheet for Classes, I see:
Type and value
Surprise, a class can be used as both a type and a value:
const a: Bag = new Bag(); // ^-Type ^-Value
So, be careful to not do this:
class C implements Bag {}
But I don't see what's wrong with a class C
implementing a type Bag
. Is the problem that a class can only implement an interface, and Bag
is assumed to be a class here? The docs on implements
only mention interface
s, so that seems to be the case.
If that's the case, then what about what I see in the next column of the cheat sheet?
Common Syntax
class User extends Account implements Updatable, Serializable { // ^- Ensures that the class // conforms to a set of // interfaces or types ...
(The comment is specifically pointing to the implements
types (Updatable
and Serializable
), and not the the extends
type (Account
).)
If a class can only implement an interface, then why does this comment mention "interfaces or types"? Shouldn't it just say "Ensures that the class conforms to a set of interfaces"? Combined with the previous vagueness, this apparent contradiction with the docs is confusing me.
Thanks!
CodePudding user response:
The distinction between "values" and "types" is that values exist at runtime, while types do not. Surprisingly for newcomers to the language, a class is both a value and a type: a value, because it describes a prototype object that will exist at runtime, and a type, because typescript can use its shape for type checking.
This means that the identifier Bar
can mean a value or a type, depending on the context in which it is used. If we write:
class C extends Bar {
Bar
refers to a value, because an extends clause expects a value, setting up a prototype inheritance between C
and Bar
that causes C
to inherit all properties of Bar
at runtime.
However, if we write:
class C implements Bar {
Bar
refers to a type, because an implements clause expects a type, expressing that C
has the same properties as Bar
in the type system, but not setting up inheritance between the prototype objects of C
and Bar
.
For instance, if Bar declares a function:
class Bar {
sayHello() {
console.log("Hello from Bar");
}
}
the code
new C().sayHello();
would compile, but throw an exception at runtime because the property C.sayHello
contains undefined
, and you can't invoke undefined
.
That why it's usually a mistake to write class C implements Bar
if Bar
refers to a class, because we most likely want runtime inheritance as well.
if a class can only implement an interface, then why does this comment mention "interfaces or types"? Shouldn't it just say "Ensures that the class conforms to a set of interfaces"? Combined with the previous vagueness, this apparent contradiction with the docs is confusing me.
First, there is no contradiction. We are allowed to write implements Bar
, even though it may not do what we want.
And the main reason they mention types here is that not all types are interfaces. For instance, you can implement an intersection type:
interface Employee {
name: string;
salary: number;
}
interface Student {
name: string;
gradeAverage: number;
}
type Intern = Employee & Student;
class John implements Intern {
name = "John Doe";
salary = 60000;
gradeAverage = 3.9;
}
why the cheat sheet authors found it necessary to include a disclaimer about an edge-case that the Typescript compiler itself will catch
Probably because it helps with understanding the error message, and because the compiler can not catch all cases. For instance, if you do:
class Marker {
// no members, used for instanceof only
}
class C implements Marker {
}
will compile just fine, but new C() instanceof Marker
will return false
.
CodePudding user response:
Edit: I don't think this is the correct answer.
I was not aware that this example and disclaimer, which appeared under the heading "Type and value" related to the previous sections about private members.
If you try to implement a class having private members (typed private or implemented as private), Typescript will return an error:
class Bag {
private test = "test";
#hidden = "hidden";
}
class C implements Bag {
// ^- Class 'C' incorrectly implements class 'Bag'. Did you mean to extend
// 'Bag' and inherit its members as a subclass?
// Property '#hidden' in type 'C' refers to a different member that
// cannot be accessed from within type 'Bag'.(2720)
private test = "test";
#hidden = "hidden";
}
(Although this does raise the question of why the cheat sheet authors found it necessary to include a disclaimer about an edge-case that the Typescript compiler itself will catch. Maybe they wrote the cheat sheet before that error was implemented.)