Skip to content

Commit 369f7c2

Browse files
committed
Twoslashed 08
1 parent 4f69ecf commit 369f7c2

File tree

1 file changed

+111
-36
lines changed

1 file changed

+111
-36
lines changed

book-content/chapters/08-classes.md

Lines changed: 111 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,16 @@ To create a class, you use the `class` keyword followed by the name of the class
1212

1313
We'll start creating the `Album` class in a similar way to how a type or interface is created:
1414

15-
```typescript
15+
```ts twoslash
16+
// @errors: 2564
1617
class Album {
17-
title: string; // red squiggly line under title
18-
artist: string; // red squiggly line under artist
19-
releaseYear: number; // red squiggly line under releaseYear
18+
title: string;
19+
artist: string;
20+
releaseYear: number;
2021
}
2122
```
2223

23-
At this point, even though it looks like a type or interface, TypeScript gives an error for each property in the class:
24-
25-
```typescript
26-
// hovering over title shows:
27-
28-
// Property 'title' has no initializer and is not definitely assigned in the constructor.
29-
```
30-
24+
At this point, even though it looks like a type or interface, TypeScript gives an error for each property in the class.
3125
How do we fix this?
3226

3327
### Adding a Constructor
@@ -232,11 +226,15 @@ class Album {
232226

233227
Now if we try to access the `rating` property from outside of the class, TypeScript will give us an error:
234228

235-
```typescript
236-
console.log(loopFindingJazzRecords.rating); // red squiggly line under rating
229+
```ts twoslash
230+
// @errors: 2341
231+
class Album {
232+
private rating = 0;
233+
}
237234

238-
// hovering over rating shows:
239-
// Property 'rating' is private and only accessible within class 'Album'.
235+
const loopFindingJazzRecords = new Album();
236+
// ---cut---
237+
console.log(loopFindingJazzRecords.rating);
240238
```
241239

242240
However, this doesn't actually prevent it from being accessed at runtime - `private` is just a compile-time annotation. You could suppress the error using a `@ts-ignore` (which we'll look at later) and still access the property:
@@ -260,13 +258,28 @@ The `#` syntax behaves the same as `private`, but it's a newer feature that's pa
260258

261259
Attempting to access a `#`-prefixed property from outside of the class will result in a syntax error:
262260

263-
```typescript
261+
```ts twoslash
262+
// @errors: 18013
263+
class Album {
264+
#rating = 0;
265+
}
266+
267+
const loopFindingJazzRecords = new Album();
268+
// ---cut---
264269
console.log(loopFindingJazzRecords.#rating); // SyntaxError
265270
```
266271

267-
Attempting to cheat by accessing it with a dynamic string will return `undefined`:
272+
Attempting to cheat by accessing it with a dynamic string will return `undefined` - and still give a TypeScript error.
268273

269-
```typescript
274+
```ts twoslash
275+
// @errors: 7053
276+
class Album {
277+
#rating = 0;
278+
}
279+
280+
const loopFindingJazzRecords = new Album();
281+
282+
// ---cut---
270283
console.log(loopFindingJazzRecords["#rating"]); // Output: undefined
271284
```
272285

@@ -519,9 +532,17 @@ Note that the `I` prefix is used to indicate an interface, while a `T` indicates
519532

520533
With the interface created, we can use the `implements` keyword to associate it with the `Album` class.
521534

522-
```typescript
535+
```ts twoslash
536+
// @errors: 2420
537+
interface IAlbum {
538+
title: string;
539+
artist: string;
540+
releaseYear: number;
541+
trackList: string[];
542+
}
543+
544+
// ---cut---
523545
class Album implements IAlbum {
524-
// red squiggly line under Album
525546
title: string;
526547
artist: string;
527548
releaseYear: number;
@@ -592,12 +613,31 @@ abstract class AlbumBase {
592613

593614
But if you try to create an instance of the `AlbumBase` class, TypeScript will give you an error:
594615

595-
```typescript
616+
```ts twoslash
617+
// @errors: 2511
618+
abstract class AlbumBase {
619+
title: string;
620+
artist: string;
621+
releaseYear: number;
622+
trackList: string[] = [];
623+
624+
constructor(opts: { title: string; artist: string; releaseYear: number }) {
625+
this.title = opts.title;
626+
this.artist = opts.artist;
627+
this.releaseYear = opts.releaseYear;
628+
}
629+
630+
addTrack(track: string) {
631+
this.trackList.push(track);
632+
}
633+
}
634+
635+
// ---cut---
596636
const albumBase = new AlbumBase({
597637
title: "Unknown Album",
598638
artist: "Unknown Artist",
599639
releaseYear: 0,
600-
}); // red squiggly line under AlbumBase
640+
});
601641
```
602642

603643
Instead, you'd need to create a class that extends the `AlbumBase` class:
@@ -658,12 +698,18 @@ Inside of a test case, we instantiate the class by calling `new CanvasNode()`.
658698

659699
However, have some errors since we expect it to house two properties, specifically `x` and `y`, each with a default value of `0`:
660700

661-
```typescript
701+
```ts twoslash
702+
// @errors: 2339
703+
import { it, expect } from "vitest";
704+
705+
class CanvasNode {}
706+
707+
// ---cut---
662708
it("Should store some basic properties", () => {
663709
const canvasNode = new CanvasNode();
664710

665-
expect(canvasNode.x).toEqual(0); // red squiggly line under x
666-
expect(canvasNode.y).toEqual(0); // red squiggly line under y
711+
expect(canvasNode.x).toEqual(0);
712+
expect(canvasNode.y).toEqual(0);
667713

668714
// @ts-expect-error Property is readonly
669715
canvasNode.x = 10;
@@ -690,14 +736,21 @@ class CanvasNode {
690736

691737
There is a test case for being able to move the `CanvasNode` object to a new location:
692738

693-
```typescript
739+
```ts twoslash
740+
// @errors: 2339
741+
import { it, expect } from "vitest";
742+
class CanvasNode {
743+
x = 0;
744+
y = 0;
745+
}
746+
// ---cut---
694747
it("Should be able to move to a new location", () => {
695748
const canvasNode = new CanvasNode();
696749

697750
expect(canvasNode.x).toEqual(0);
698751
expect(canvasNode.y).toEqual(0);
699752

700-
canvasNode.move(10, 20); // red squiggly line under move
753+
canvasNode.move(10, 20);
701754

702755
expect(canvasNode.x).toEqual(10);
703756
expect(canvasNode.y).toEqual(20);
@@ -731,15 +784,34 @@ class CanvasNode {
731784

732785
In these test cases, there are errors accessing the `position` property since it is not currently a property of the `CanvasNode` class:
733786

734-
```typescript
787+
```ts twoslash
788+
// @errors: 2339
789+
import { it, expect } from "vitest";
790+
791+
class CanvasNode {
792+
x: number;
793+
y: number;
794+
795+
constructor(position?: { x: number; y: number }) {
796+
this.x = position?.x ?? 0;
797+
this.y = position?.y ?? 0;
798+
}
799+
800+
move(x: number, y: number) {
801+
this.x = x;
802+
this.y = y;
803+
}
804+
}
805+
806+
// ---cut---
735807
it("Should be able to move", () => {
736808
const canvasNode = new CanvasNode();
737809

738-
expect(canvasNode.position).toEqual({ x: 0, y: 0 }); // red squiggly line under position
810+
expect(canvasNode.position).toEqual({ x: 0, y: 0 });
739811

740812
canvasNode.move(10, 20);
741813

742-
expect(canvasNode.position).toEqual({ x: 10, y: 20 }); // red squiggly line under position
814+
expect(canvasNode.position).toEqual({ x: 10, y: 20 });
743815
});
744816

745817
it("Should be able to receive an initial position", () => {
@@ -748,7 +820,7 @@ it("Should be able to receive an initial position", () => {
748820
y: 20,
749821
});
750822

751-
expect(canvasNode.position).toEqual({ x: 10, y: 20 }); // red squiggly line under position
823+
expect(canvasNode.position).toEqual({ x: 10, y: 20 });
752824
});
753825
```
754826

@@ -776,11 +848,14 @@ class CanvasNode {
776848

777849
The `#` in front of the `x` and `y` properties means they are `readonly` and can't be modified directly outside of the class. In addition, when a getter is present without a setter, its property will also be treated as `readonly`, as seen in this test case:
778850

779-
```typescript
780-
canvasNode.position = { x: 10, y: 20 }; // red squiggly line under position
851+
```ts twoslash
852+
// @errors: 2540
853+
declare const canvasNode: {
854+
readonly position: { x: number; y: number };
855+
};
781856

782-
// hovering over position shows:
783-
// Cannot assign to 'position' because it is a read-only property.
857+
// ---cut---
858+
canvasNode.position = { x: 10, y: 20 };
784859
```
785860

786861
Your task is to write a setter for the `position` property that will allow for the test case to pass.

0 commit comments

Comments
 (0)