Skip to content

Commit 68cb1f5

Browse files
committed
Added some exercises to 05
1 parent b2fdf49 commit 68cb1f5

File tree

4 files changed

+163
-2
lines changed

4 files changed

+163
-2
lines changed

book-content/chapters/04-essential-types-and-annotations.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# 04. Essential Types and Annotations
2-
31
Now we've covered most of the why of TypeScript, it's time to start with the how. We'll cover key concepts like type annotations and type inference, as well as how to start writing type-safe functions.
42

53
It's important to build a solid foundation, as everything you'll learn later builds upon what you'll learn in this chapter.

book-content/chapters/05-unions-literals-and-narrowing.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,11 @@ Normally we wouldn't explicitly call the `getUsername` function with `null`, but
179179

180180
Currently, the `username` parameter only accepts a `string` type, and the check for `null` isn't doing anything. Update the function parameter's type so the errors are resolved and the function can handle `null` .
181181

182+
<Exercise
183+
title="Exercise 1: `string` or `null`"
184+
filePath="/src/018-unions-and-narrowing/053-introduction-to-unions.problem.ts"
185+
/>
186+
182187
#### Exercise 2: Restricting Function Parameters
183188

184189
Here we have a `move` function that takes in a `direction` of type string, and a `distance` of type number:
@@ -227,6 +232,11 @@ move(
227232

228233
Your challenge is to update the `move` function so that it only accepts the strings `"up"`, `"down"`, `"left"`, and `"right"`. This way, TypeScript will throw an error when we try to pass in any other string.
229234

235+
<Exercise
236+
title="Exercise 2: Restricting Function Parameters"
237+
filePath="/src/018-unions-and-narrowing/054-literal-types.problem.ts"
238+
/>
239+
230240
#### Solution 1: `string` or `null`
231241

232242
The solution is to update the `username` parameter to be a union of `string` and `null`:
@@ -441,6 +451,11 @@ it("Should return false for null", () => {
441451

442452
Your task is to rewrite the `validateUsername` function to add narrowing so that the `null` case is handled and the tests all pass.
443453

454+
<Exercise
455+
title="Exercise 1: Narrowing with `if` Statements"
456+
filePath="/src/018-unions-and-narrowing/059-narrowing-with-if-statements.problem.ts"
457+
/>
458+
444459
#### Exercise 2: Throwing Errors to Narrow
445460

446461
Here we have a line of code that uses `document.getElementById` to fetch an HTML element, which can return either an `HTMLElement` or `null`:
@@ -463,6 +478,11 @@ type Test = Expect<Equal<typeof appElement, HTMLElement>>;
463478

464479
Your task is to use `throw` to narrow down the type of `appElement` before it's checked by the test.
465480

481+
<Exercise
482+
title="Exercise 2: Throwing Errors to Narrow"
483+
filePath="/src/018-unions-and-narrowing/062-throwing-errors-to-narrow.problem.ts"
484+
/>
485+
466486
#### Exercise 3: Using `in` to Narrow
467487

468488
Here we have a `handleResponse` function that takes in a type of `APIResponse`, which is a union of two types of objects.
@@ -525,6 +545,11 @@ Your challenge is to find the correct syntax for narrowing down the types within
525545

526546
The changes should happen inside of the function without modifying any other parts of the code.
527547

548+
<Exercise
549+
title="Exercise 3: Using `in` to Narrow"
550+
filePath="/src/018-unions-and-narrowing/064-narrowing-with-in-statements.problem.ts"
551+
/>
552+
528553
#### Solution 1: Narrowing with `if` Statements
529554

530555
There are several different ways to validate the username length.
@@ -921,6 +946,11 @@ console.log(error.message);
921946

922947
Your task is to update the `if` statement to have the proper condition to check if the `error` has a message attribute before logging it. Check the title of the exercise to get a hint... And remember, `Error` is a class.
923948

949+
<Exercise
950+
title="Exercise 1: Narrowing Errors with `instanceof`"
951+
filePath="/src/018-unions-and-narrowing/065.5-narrowing-with-instanceof-statements.problem.ts"
952+
/>
953+
924954
#### Exercise 2: Narrowing `unknown` to a Value
925955

926956
Here we have a `parseValue` function that takes in a `value` of type `unknown`:
@@ -962,6 +992,11 @@ it("Should error when anything else is passed in", () => {
962992

963993
Your challenge is to modify the `parseValue` function so that the tests pass and the errors go away. I want you to challenge yourself to do this _only_ by narrowing the type of `value` inside of the function. No changes to the types. This will require a very large `if` statement!
964994

995+
<Exercise
996+
title="Exercise 2: Narrowing `unknown` to a Value"
997+
filePath="/src/018-unions-and-narrowing/066-narrowing-unknown-to-a-value.problem.ts"
998+
/>
999+
9651000
#### Exercise 3: Reusable Type Guards
9661001

9671002
Let's imagine that we have two very similar functions, each with a long conditional check to narrow down the type of a value.
@@ -1010,6 +1045,11 @@ Both functions have the same conditional check. This is a great opportunity to c
10101045

10111046
All the tests are currently passing. Your job is to try to refactor the two functions to use a reusable type guard, and remove the duplicated code. As it turns out, TypeScript makes this a lot easier than you expect.
10121047

1048+
<Exercise
1049+
title="Exercise 3: Reusable Type Guards"
1050+
filePath="/src/018-unions-and-narrowing/066.5-reusable-type-guards.problem.ts"
1051+
/>
1052+
10131053
#### Solution 1: Narrowing Errors with `instanceof`
10141054

10151055
The way to solve this challenge is to narrow the `error` using the `instanceof` operator.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { expect, it } from "vitest";
2+
3+
const parseValue = (value: unknown) => {
4+
if (
5+
typeof value === "object" &&
6+
value !== null &&
7+
"data" in value &&
8+
typeof value.data === "object" &&
9+
value.data !== null &&
10+
"id" in value.data &&
11+
typeof value.data.id === "string"
12+
) {
13+
return value.data.id;
14+
}
15+
16+
throw new Error("Parsing error!");
17+
};
18+
19+
const parseValueAgain = (value: unknown) => {
20+
if (
21+
typeof value === "object" &&
22+
value !== null &&
23+
"data" in value &&
24+
typeof value.data === "object" &&
25+
value.data !== null &&
26+
"id" in value.data &&
27+
typeof value.data.id === "string"
28+
) {
29+
return value.data.id;
30+
}
31+
32+
throw new Error("Parsing error!");
33+
};
34+
35+
it("parseValue should handle a { data: { id: string } }", () => {
36+
const result = parseValue({
37+
data: {
38+
id: "123",
39+
},
40+
});
41+
42+
expect(result).toBe("123");
43+
});
44+
45+
it("parseValue should error when anything else is passed in", () => {
46+
expect(() => parseValue("123")).toThrow("Parsing error!");
47+
expect(() => parseValue(123)).toThrow("Parsing error!");
48+
});
49+
50+
it("parseValueAgain should handle a { data: { id: string } }", () => {
51+
const result = parseValueAgain({
52+
data: {
53+
id: "123",
54+
},
55+
});
56+
57+
expect(result).toBe("123");
58+
});
59+
60+
it("parseValueAgain should error when anything else is passed in", () => {
61+
expect(() => parseValueAgain("123")).toThrow("Parsing error!");
62+
expect(() => parseValueAgain(123)).toThrow("Parsing error!");
63+
});
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { expect, it } from "vitest";
2+
3+
// TODO - remove return type in 5.5
4+
const hasDataId = (value: unknown): value is { data: { id: string } } => {
5+
return (
6+
typeof value === "object" &&
7+
value !== null &&
8+
"data" in value &&
9+
typeof value.data === "object" &&
10+
value.data !== null &&
11+
"id" in value.data &&
12+
typeof value.data.id === "string"
13+
);
14+
};
15+
16+
const parseValue = (value: unknown) => {
17+
if (hasDataId(value)) {
18+
return value.data.id;
19+
}
20+
21+
throw new Error("Parsing error!");
22+
};
23+
24+
const parseValueAgain = (value: unknown) => {
25+
if (hasDataId(value)) {
26+
return value.data.id;
27+
}
28+
29+
throw new Error("Parsing error!");
30+
};
31+
32+
it("parseValue should handle a { data: { id: string } }", () => {
33+
const result = parseValue({
34+
data: {
35+
id: "123",
36+
},
37+
});
38+
39+
expect(result).toBe("123");
40+
});
41+
42+
it("parseValue should error when anything else is passed in", () => {
43+
expect(() => parseValue("123")).toThrow("Parsing error!");
44+
expect(() => parseValue(123)).toThrow("Parsing error!");
45+
});
46+
47+
it("parseValueAgain should handle a { data: { id: string } }", () => {
48+
const result = parseValueAgain({
49+
data: {
50+
id: "123",
51+
},
52+
});
53+
54+
expect(result).toBe("123");
55+
});
56+
57+
it("parseValueAgain should error when anything else is passed in", () => {
58+
expect(() => parseValueAgain("123")).toThrow("Parsing error!");
59+
expect(() => parseValueAgain(123)).toThrow("Parsing error!");
60+
});

0 commit comments

Comments
 (0)