-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
022b9e3
commit 0a63224
Showing
3 changed files
with
528 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,77 @@ | ||
# Exercise: Blast from the Past | ||
# Exercise: Blast from the Past 🏛️ | ||
|
||
> **Section 1: Composing Types: Part I** | ||
In this exercise we're going to build up the types for objects which | ||
contain data on ancient civilizations. We'll use generic and conditional | ||
types to help us. | ||
types to help us along the way. | ||
|
||
## Create the `Civilization` type | ||
|
||
```typescript | ||
interface Civilization { | ||
name: string; | ||
location: string; | ||
} | ||
``` | ||
|
||
## Turn the `Civilization` type into a generic type | ||
|
||
- Add a type parameter to the `Civilization` type: | ||
|
||
```diff | ||
-interface Civilization { | ||
+interface Civilization<NotablePeopleType> { | ||
``` | ||
|
||
- Add a property which uses the type variable: | ||
|
||
```typescript | ||
notablePeople: NotablePeopleType[]; | ||
``` | ||
|
||
## Create types for all the notable people | ||
|
||
```typescript | ||
interface Person { | ||
name: string; | ||
occupation: string; | ||
} | ||
|
||
type Architect = Person & { occupation: 'Architect' }; | ||
type Pharaoh = Person & { occupation: 'Pharaoh' }; | ||
type Poet = Person & { occupation: 'Poet' }; | ||
type Philosopher = Person & { occupation: 'Philosopher' }; | ||
type General = Person & { occupation: 'General' }; | ||
``` | ||
|
||
## Add type annotations to each civilization object | ||
|
||
Example: | ||
|
||
```typescript | ||
const egyptianCivilization: Civilization<Architect | Pharaoh> = { | ||
``` | ||
## Notice that we can pass anything in for the `NotablePeopleType` argument | ||
Example: | ||
```typescript | ||
Civilization<NotablePeople<Architect | true>> | ||
``` | ||
## Create a generic type with a conditional type | ||
```typescript | ||
type NotablePeople<PersonType> = PersonType extends Person ? PersonType : never; | ||
``` | ||
## Update the type annotations for all civilization objects | ||
Example: | ||
```diff | ||
-const egyptianCivilization: Civilization<Architect | Pharaoh> = { | ||
+const egyptianCivilization: Civilization<NotablePeople<Architect | Pharaoh>> = { | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,101 @@ | ||
# Exercise: Tree-mendous Mapped Types | ||
# Exercise: Tree-mendous Mapped Types 🌳 | ||
|
||
> **Section 1: Composing Types: Part II** | ||
In this exercise we're going to use one object type to create | ||
another object type. It's tree-mendously powerful! | ||
another object type. We'll see just how tree-mendously powerful | ||
mapped types really are! | ||
|
||
## Part 1 | ||
|
||
### Create a `Tree` type | ||
|
||
```typescript | ||
type Tree = { | ||
name: string; | ||
height: number; | ||
age: number; | ||
}; | ||
``` | ||
|
||
### Use the type | ||
|
||
```diff | ||
-const oakData = { | ||
+const oakData: Tree = { | ||
``` | ||
|
||
### Create `TreeDetails` mapped type to describe `oak` object | ||
|
||
```typescript | ||
type TreeDetails<Type> = { | ||
[Key in keyof Type as `get${Capitalize<string & Key>}`]: () => Type[Key]; | ||
}; | ||
|
||
type OakTree = TreeDetails<Tree>; | ||
``` | ||
|
||
### Components to discuss | ||
|
||
- `keyof Type` | ||
- `Key in keyof Type` — same as `Key in "name" | "height" | "age"` | ||
- `as` - type assertion | ||
- `Capitalize<string & Key>` — using the `Capitalize` utility type | ||
- `string & Key` - ensures that the `Key` extends the `string` type, as keys can be Symbols in JavaScript | ||
- `() => Type[Key]` - function signature | ||
- `Type[Key]` - indexed access type to look up type of property on `Type` | ||
|
||
### Alternative for extracting keys with type string | ||
|
||
```typescript | ||
Extract<keyof Type, string> | ||
``` | ||
|
||
### Use the `OakTree` type | ||
|
||
```diff | ||
-let oak = { | ||
+let oak: OakTree = { | ||
getName: () => oakData.name, | ||
getHeight: () => oakData.height, | ||
getAge: () => oakData.age, | ||
}; | ||
``` | ||
|
||
## Optional: Part 2: Create a `PartialTree` mapped type | ||
|
||
```typescript | ||
type PartialTree<Type> = { | ||
[Key in keyof Type]+?: Type[Key]; | ||
}; | ||
|
||
// Using the `+?` optionality modifier to indicate the field is optional. | ||
|
||
type PartialOak = PartialTree<Tree>; | ||
|
||
let partialOak: PartialOak = { | ||
name: "Oak", | ||
}; | ||
|
||
console.log({ partialOak }); | ||
``` | ||
|
||
## Optional: Part 3: Create a generic type with a template literal type | ||
|
||
```typescript | ||
type TreeWithAge<Type extends number> = `Tree with age ${Type}`; | ||
|
||
type OldTree = TreeWithAge<number>; | ||
|
||
let oldOak: OldTree = `Tree with age ${oak.getAge()}`; | ||
|
||
console.log(oldOak); | ||
``` | ||
|
||
### Alternative to hardcoding `number` as the argument to `TreeWithAge` | ||
|
||
Use an indexed access type: | ||
|
||
```typescript | ||
ReturnType<OakTree["getAge"]> | ||
``` |
Oops, something went wrong.