Skip to content

Commit 647b244

Browse files
committed
Changed utils folder
1 parent 23dde8b commit 647b244

File tree

1 file changed

+64
-28
lines changed

1 file changed

+64
-28
lines changed

book-content/chapters/16-the-utils-folder.md

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -326,14 +326,14 @@ Note how clever TypeScript is being here. Even though we didn't specify a return
326326

327327
## Type Predicates
328328

329-
A type predicate is a special return type that tells TypeScript that a function returns a Boolean value that says something about the type of one of its parameters.
329+
We were introduced to type predicates way back in chapter 5, when we looked at narrowing. They're used to capture reusable logic that narrows the type of a variable.
330330

331331
For example, say we want to ensure that a variable is an `Album` before we try accessing its properties or passing it to a function that requires an `Album`.
332332

333-
We can write an `isAlbum` function that takes in an input, and specifies a return type of `input is Album`. The body of the function will check that the `input` passed in is a non-null object with the required properties of an `Album`:
333+
We can write an `isAlbum` function that takes in an input, and checks for all the required properties.
334334

335335
```typescript
336-
function isAlbum(input: unknown): input is Album {
336+
function isAlbum(input: unknown) {
337337
return (
338338
typeof input === "object" &&
339339
input !== null &&
@@ -345,55 +345,91 @@ function isAlbum(input: unknown): input is Album {
345345
}
346346
```
347347

348-
The `input is Album` return type is the type predicate that tells TypeScript that if the function returns `true`, then the `input` parameter is of type `Album`. Otherwise, it isn't.
348+
If we hover over `isAlbum`, we can see a rather ugly type signature:
349349

350-
### Narrowing with Type Predicates
350+
```typescript
351+
// hovering over isAlbum shows:
352+
function isAlbum(
353+
input: unknown,
354+
): input is object &
355+
Record<"id", unknown> &
356+
Record<"title", unknown> &
357+
Record<"artist", unknown> &
358+
Record<"year", unknown>;
359+
```
351360

352-
Type predicates are often used in conditional statements to narrow the type of a variable to become more specific.
361+
This is technically correct: a big intersection between an `object` and a bunch of `Record`s. But it's not very helpful.
353362

354-
For example, we can use the `isAlbum` type predicate to check if an `item` is an `Album` before accessing its `title` property:
363+
When we try to use `isAlbum` to narrow the type of a value, TypeScript will infer lots of the values as `unknown`:
355364

356365
```typescript
357-
function getAlbumTitle(item: unknown) {
358-
if (isAlbum(item)) {
359-
return item.title;
366+
const run = (maybeAlbum: unknown) => {
367+
if (isAlbum(maybeAlbum)) {
368+
maybeAlbum.name.toUpperCase(); // red squiggly line under name
360369
}
361-
return "Unknown Album";
362-
}
370+
};
371+
372+
// hovering over name shows:
373+
// Object is of type 'unknown'.
363374
```
364375

365-
In this case, the `getAlbumTitle` function takes an `item` of type `unknown`. Inside the function, we use the `isAlbum` type predicate to check if the `item` is an `Album`. If it is, TypeScript narrows the type of `item` to `Album` within the conditional block, allowing us to access the `title` property without any type errors:
376+
To fix this, we'd need to add even more checks to `isAlbum` to ensure we're checking the types of all the properties:
366377

367378
```typescript
368-
let title = getAlbumTitle({
369-
id: 1,
370-
title: "Dummy",
371-
artist: "Portishead",
372-
year: 1994,
373-
});
374-
375-
console.log(title); // "Dummy"
379+
function isAlbum(input: unknown) {
380+
return (
381+
typeof input === "object" &&
382+
input !== null &&
383+
"id" in input &&
384+
"title" in input &&
385+
"artist" in input &&
386+
"year" in input &&
387+
typeof input.id === "number" &&
388+
typeof input.title === "string" &&
389+
typeof input.artist === "string" &&
390+
typeof input.year === "number"
391+
);
392+
}
393+
```
376394

377-
let notAnAlbumTitle = getAlbumTitle("Some string");
395+
This can feel far too verbose. We can make it more readable by adding our own type predicate.
378396

379-
console.log(notAnAlbumTitle); // "Unknown Album"
397+
```typescript
398+
function isAlbum(input: unknown): input is Album {
399+
return (
400+
typeof input === "object" &&
401+
input !== null &&
402+
"id" in input &&
403+
"title" in input &&
404+
"artist" in input &&
405+
"year" in input
406+
);
407+
}
380408
```
381409

382-
### Type Predicates Can be Unsafe
410+
Now, when we use `isAlbum`, TypeScript will know that the type of the value has been narrowed to `Album`:
383411

384-
Type predicates are a great technique to be aware of, and are particularly useful when working with union types.
412+
```typescript
413+
const run = (maybeAlbum: unknown) => {
414+
if (isAlbum(maybeAlbum)) {
415+
maybeAlbum.name.toUpperCase(); // No error!
416+
}
417+
};
418+
```
385419

386-
However, there are situations where they aren't as type-safe as they may appear.
420+
For complex type guards, this can be much more readable.
421+
422+
### Type Predicates Can be Unsafe
387423

388-
For example, if the type predicate doesn't match the actual type being checked, TypeScript won't catch that discrepancy:
424+
Authoring your own type predicates can be a little dangerous. If the type predicate doesn't accurately reflect the type being checked, TypeScript won't catch that discrepancy:
389425

390426
```typescript
391427
function isAlbum(input): input is Album {
392428
return typeof input === "object";
393429
}
394430
```
395431

396-
In this case, any object passed to `isAlbum` will be considered an `Album`, even if it doesn't have the required properties. This is a common pitfall when working with type predicates, and it's important to ensure that the type predicate accurately reflects the type being checked.
432+
In this case, any object passed to `isAlbum` will be considered an `Album`, even if it doesn't have the required properties. This is a common pitfall when working with type predicates - it's important to consider them about as unsafe as `as` and `!`.
397433

398434
## Assertion Functions
399435

0 commit comments

Comments
 (0)