-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: initial
Are you sure you want to change the base?
Conversation
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
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:
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"`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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"`. |
There was a problem hiding this comment.
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"`. |
There was a problem hiding this comment.
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`: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} else if (typeof year === "number") { | |
} else { |
function validateUsername(username: string | null): boolean { | ||
return username.length > 5; | ||
|
||
return false; |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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"` |
There was a problem hiding this comment.
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'
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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`. |
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
No description provided.