You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: book-content/chapters/05-unions-literals-and-narrowing.md
+31-47Lines changed: 31 additions & 47 deletions
Original file line number
Diff line number
Diff line change
@@ -973,53 +973,43 @@ Your challenge is to modify the `parseValue` function so that the tests pass and
973
973
974
974
#### Exercise 3: Reusable Type Guards
975
975
976
-
Let's imagine that we have two very similar functions, each with a long conditional check to narrow down the type of a value.
976
+
Let's imagine that we have two functions which both take in a `value` of type `unknown`, and attempt to parse that value to an array of strings.
977
977
978
-
Here's the first function:
978
+
Here's the first function, which joins an array of names together into a single string:
979
979
980
980
```typescript
981
-
const parseValue = (value:unknown) => {
982
-
if (
983
-
typeofvalue==="object"&&
984
-
value!==null&&
985
-
"data"invalue&&
986
-
typeofvalue.data==="object"&&
987
-
value.data!==null&&
988
-
"id"invalue.data&&
989
-
typeofvalue.data.id==="string"
990
-
) {
991
-
returnvalue.data.id;
981
+
const joinNames = (value:unknown) => {
982
+
if (Array.isArray(value) &&value.every((item) =>typeofitem==="string")) {
983
+
returnvalue.join("");
992
984
}
993
985
994
986
thrownewError("Parsing error!");
995
987
};
996
988
```
997
989
998
-
And here's the second function:
990
+
And here's the second function, which maps over the array of names and adds a prefix to each one:
999
991
1000
992
```typescript
1001
-
const parseValueAgain = (value:unknown) => {
1002
-
if (
1003
-
typeofvalue==="object"&&
1004
-
value!==null&&
1005
-
"data"invalue&&
1006
-
typeofvalue.data==="object"&&
1007
-
value.data!==null&&
1008
-
"id"invalue.data&&
1009
-
typeofvalue.data.id==="string"
1010
-
) {
1011
-
returnvalue.data.id;
993
+
const createSections = (value:unknown) => {
994
+
if (Array.isArray(value) &&value.every((item) =>typeofitem==="string")) {
995
+
returnvalue.map((item) =>`Section: ${item}`);
1012
996
}
1013
997
1014
998
thrownewError("Parsing error!");
1015
999
};
1016
1000
```
1017
1001
1018
-
Both functions have the same conditional check. This is a great opportunity to create a reusable type guard.
1002
+
Both functions have the same conditional check:
1003
+
1004
+
```ts
1005
+
if (Array.isArray(value) &&value.every((item) =>typeofitem==="string")) {
1006
+
```
1007
+
1008
+
This is a great opportunity to create a reusable type guard.
1019
1009
1020
1010
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.
1021
1011
1022
-
<Exercisetitle="Exercise 3: Reusable Type Guards"filePath="/src/018-unions-and-narrowing/066.5-reusable-type-guards.problem.ts"></Exercise>
1012
+
<Exercise title="Exercise 3: Reusable Type Guards" filePath="/src/018-unions-and-narrowing/072.5-reusable-type-guards.problem.ts"></Exercise>
1023
1013
1024
1014
#### Solution 1: Narrowing Errors with `instanceof`
1025
1015
@@ -1168,18 +1158,12 @@ This is usually _not_ how you'd want to write your code. It's a bit of a mess. Y
1168
1158
1169
1159
#### Solution 3: Reusable Type Guards
1170
1160
1171
-
The first step is to create a function called `hasDataId` that captures the conditional check:
1161
+
The first step is to create a function called `isArrayOfStrings` that captures the conditional check:
@@ -1189,33 +1173,33 @@ We haven't given `value` a type here - `unknown` makes sense, because it could b
1189
1173
Now we can refactor the two functions to use this type guard:
1190
1174
1191
1175
```typescript
1192
-
constparseValue= (value:unknown) => {
1193
-
if (hasDataId(value)) {
1194
-
returnvalue.data.id;
1176
+
constjoinNames= (value:unknown) => {
1177
+
if (isArrayOfStrings(value)) {
1178
+
returnvalue.join("");
1195
1179
}
1196
1180
1197
1181
thrownewError("Parsing error!");
1198
1182
};
1199
1183
1200
-
constparseValueAgain= (value:unknown) => {
1201
-
if (hasDataId(value)) {
1202
-
returnvalue.data.id;
1184
+
constcreateSections= (value:unknown) => {
1185
+
if (isArrayOfStrings(value)) {
1186
+
returnvalue.map((item) =>`Section: ${item}`);
1203
1187
}
1204
1188
1205
1189
thrownewError("Parsing error!");
1206
1190
};
1207
1191
```
1208
1192
1209
-
Incredibly, this is all TypeScript needs to be able to narrow the type of `value` inside of the `if` statement. It's smart enough to understand that `hasDataId` being called on `value` ensures that `value`has a `data` property with an `id` property.
1193
+
Incredibly, this is all TypeScript needs to be able to narrow the type of `value` inside of the `if` statement. It's smart enough to understand that `isArrayOfStrings` being called on `value` ensures that `value`is an array of strings.
1210
1194
1211
-
We can observe this by hovering over `hasDataId`:
1195
+
We can observe this by hovering over `isArrayOfStrings`:
This return type we're seeing is a type predicate. It's a way of saying "if this function returns `true`, then the type of the value is `{ data: { id: string } }`".
1202
+
This return type we're seeing is a type predicate. It's a way of saying "if this function returns `true`, then the type of the value is `string[]`".
1219
1203
1220
1204
We'll look at authoring our own type predicates in one of the later chapters in the book - but it's very useful that TypeScript infers its own.
0 commit comments