Skip to content

Chapter 5 #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: initial
Choose a base branch
from
Open

Chapter 5 #5

wants to merge 1 commit into from

Conversation

Andarist
Copy link
Owner

@Andarist Andarist commented Jul 8, 2024

No description provided.

This situation comes up in JavaScript all the time. Imagine you have a value that is a `string` on Tuesdays, but `null` the rest of the time:

```ts twoslash
const message = Date.now() % 2 === 0 ? "Hello Tuesdays!" : null;
Copy link
Owner Author

@Andarist Andarist Jul 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const message = Date.now() % 2 === 0 ? "Hello Tuesdays!" : null;
const message = new Date().getDay() === 2 ? "Hello Tuesdays!" : null;

logId(true);
```

To create a union type, the `|` operator is used to separate the types. Each type of the union is called a 'member' of the union.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they are also often called constituents if im not mistaken


Union types can be used in many different ways, and they're a powerful tool for creating flexible type definitions.

### Literal Types
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it feels slightly off that this section is about literal types and then examples are focused on unions of literal types, it seems like this takes a small shortcut in introducing concepts. To fix that maybe you could at least rename this header to smth like "Literal types unions" and just add a sentence or two to the content of this paragraph to make the flow slightly more... flowing 😅 Like, "let's first take a look at literal types [...] and now let's see how powerful those are when used in unions"

type PhysicalFormat = "LP" | "CD" | "Cassette";
```

We could then specify `AlbumFormat` as a union of `DigitalFormat` and `PhysicalFormat:
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
We could then specify `AlbumFormat` as a union of `DigitalFormat` and `PhysicalFormat:
We could then specify `AlbumFormat` as a union of `DigitalFormat` and `PhysicalFormat`:


To test this function, we have some `@ts-expect-error` directives that tell TypeScript we expect the following lines to throw an error.

However, since the `move` function currently takes in a `string` for the `direction` parameter, we can pass in any string we want, even if it's not a valid direction. There is also a test where we expect that passing `20` as a distance won't work, but it's being accepted as well.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by the part I removed in this suggestion:

Suggested change
However, since the `move` function currently takes in a `string` for the `direction` parameter, we can pass in any string we want, even if it's not a valid direction. There is also a test where we expect that passing `20` as a distance won't work, but it's being accepted as well.
However, since the `move` function currently takes in a `string` for the `direction` parameter, we can pass in any string we want, even if it's not a valid direction.

It seems out of the blue here, I'm not even sure how u expect to correct it later and I don't think there is any follow up question/excercise about it later on.


### Wider vs Narrower Types

Some types are wider versions of other types. For example, `string` is wider than the literal string `"small"`. This is because `string` can be any string, while `"small"` can only be the string `"small"`.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Some types are wider versions of other types. For example, `string` is wider than the literal string `"small"`. This is because `string` can be any string, while `"small"` can only be the string `"small"`.
Some types are wider versions of other types. For example, `string` is wider than the literal string `"small"`. This is because `string` can be any string (including `"small"`), while `"small"` can only be the string `"small"`.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but i guess u get to that in a sec, so there might not be a need to mention it straightaway

logSize("medium");
```

If you're familiar with the concept of 'subtypes' and 'supertypes' in set theory, this is a similar idea. `"small"` is a subtype of `string` (it is more specific), and `string` is a supertype of `"small"`.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this paragraph asks for a drawing of two circles :D


However, anywhere outside of the conditional block the type of `year` is still the union `string | number`. This is because narrowing only applies within the block's scope.

For the sale of illustration, if we add a `boolean` to the `year` union, the first `if` block will still end up with a type of `string`, but the `else` block will end up with a type of `number | boolean`:
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For the sale of illustration, if we add a `boolean` to the `year` union, the first `if` block will still end up with a type of `string`, but the `else` block will end up with a type of `number | boolean`:
For the sake of illustration, if we add a `boolean` to the `year` union, the first `if` block will still end up with a type of `string`, but the `else` block will end up with a type of `number | boolean`:

const getAlbumYear = (year: string | number | boolean) => {
if (typeof year === "string") {
console.log(`The album was released in ${year}.`); // `year` is string
} else if (typeof year === "number") {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else if (typeof year === "number") {
} else {

function validateUsername(username: string | null): boolean {
return username.length > 5;

return false;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u also have this error here, it feels confusing and perhaps accidental?

Unreachable code detected.(7027)

type Test = Expect<Equal<typeof appElement, HTMLElement>>;
```

Your task is to use `throw` to narrow down the type of `appElement` before it's checked by the test.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has not been shown at all before so perhaps it might throw off the reader, I guess it really depends on the reader.

}
```

This avoids the issue with empty strings.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it sounds like it avoids it here but it avoids it in other cases for which it might matter, it's irrelevant here (which u called out above but perhaps this statement here could still be more precise)


This shows that TypeScript understands the _reverse_ of a check. Very smart.

##### Option 4: Check if `typeof username` is `"object"`
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically true... but I'd remove this option and find a different place to call out typeof null === 'object'

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you already call out this later on, so i think this option is just unnecessary and no other changes are desired here

};
```

This is certainly one way to handle it. But there is a built-in way to do it.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • to make reverse checks possible and all you should add error?: undefined to the other member

};
```

When hovering this function, TypeScript will infer that it returns `void`, indicating that it essentially returns nothing.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

void means that it returns anything, not nothing. In general, I just find void to be obscure in TS and often unknown might be a better option. You can accidentally run into issues with void cause it's "anything" but yet TS sometimes assumes it's undefined(-like?). It's very confusing :v


In the code snippet above, we have a function called `somethingDangerous` that has a 50/50 chance of either throwing an error.

Notice that the `error` variable in the `catch` clause is typed as `unknown`.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth mentioning why it's unknown here

The first step is to create a function called `isArrayOfStrings` that captures the conditional check:

```typescript
const isArrayOfStrings = (value) => {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const isArrayOfStrings = (value) => {
const isArrayOfStrings = (value: unknown) => {

const status: "error" | "success";
```

Note that this intelligent behavior is specific to discriminated tuples, and won't work with discriminated objects - as we saw in our previous exercise.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bollocks :P it didn't work only because properties were not shared among union members and then spread didn't work because spreading lost track of the relationship. This very example here would work fine with objects and destructuring - this behavior is, by no means, unique to tuples:
TS playground

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on that u might want to revisit paragraphs related to this behavior

@Andarist Andarist marked this pull request as ready for review July 14, 2024 10:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant