Type | Treat The Finale
Gabrielle Crevecoeur
Posted on October 31, 2020
Thank you so much for participating in TypeScript's Type | Treat
coding challenges! Unfortunately, we have come to the end of our spooky journey but no worries, there will be more challenges to come in the future!
Beginner/Learner Challenges
Intermediate/Advanced Challenges
Yesterday's Solution
Beginner/Learner Challenge
Like many challenges, you answer to this depends on how thorough you wanted to type the houses.
The in-challenge text tries to guide you to answer with a single generic type which passes the first argument to both trickOrTreat
and restock
.
type House<Candy> = {
doorNumber: number
trickOrTreat(): Candy;
restock(items: Candy): void;
}
type FirstHouse = House<"book" | "candy">
type SecondHouse = House<"toothbrush" | "mints">
// ... same pattern for the rest
This could be enough, and that's totally enough type safety for cases like this. This does lose the doorNumber
being exact though. So, here are two different routes to give the doorNumber
to each house:
// Via a 2nd generic argument
type House<DoorNumber, Candy> = {
doorNumber: DoorNumber
trickOrTreat(): Candy;
restock(items: Candy): void;
}
type FirstHouse = House<1, "book" | "candy">
type SecondHouse = House<2, "toothbrush" | "mints">
// ... same pattern for the rest
and
type House<Candy> = {
doorNumber: number
trickOrTreat(): Candy;
restock(items: Candy): void;
}
// Via intersection types:
type FirstHouse = House<"book" | "candy"> & { doorNumber: 1 }
type SecondHouse = House<"toothbrush" | "mints"> & { doorNumber: 2 }
Intermediate/Advanced Challenge
OK, this one is tricky. It's based on this Playground example.
We started out by making types for passing the data around
type Movies = typeof moviesToShow
type Movie = { forKids: boolean }
// Template strings literals to describe each task
type Get<T extends string> = `getVHSFor${capitalize T}`
type MakePopcorn<T extends string> = `makePopcornFor${capitalize T}`
type Play<T extends string> = `play${capitalize T }`
// A union of the above literal types
type Tasks<T extends string> = Get<T> | MakePopcorn<T> | Play<T>
These gave us a set of primitives which could work together to create this whopper:
type MakeScheduler<Type> = {
[Field in keyof Type as Tasks<Field extends string ? Field : never>]: () => void;
};
This type uses the new as
syntax for mapped types in TypeScript 4.1 to essentially map each field (Field) from the keys in the input type (Type) to the union Tasks
above. This means that each field is converted into three templated literals:
input: `"halloween"` turns to:
├─ Get<"halloween"> -> `getVHSForHalloween`
├─ MakePopcorn<"halloween"> -> `makePopcornForHalloween`
└─ Play<"halloween"> -> `playHalloween`
Which is declared to be a function which returns void.
This type is then used as the return type for the makeScheduler
function:
function makeScheduler(movies: Movies): MakeScheduler<Movies> {
For simplicities sake, we skipped typing the inside of the function - though the folks who did that, good work!
The second part added one simple constraint, but one that requires some work to get right. We wanted to take into account whether a movie was for kids of not inside the type system.
Our answer for this was to recreate the scheduler function above, and to add the logic for removing those types during the type mapping process.
type MakeKidsScheduler<Type> = {
[Field in keyof Type as Tasks<Field extends string ? Field : never>]:
Type[Field] extends { forKids: true } ? () => void : never;
};
Instead of returning a () => void
, we inserted a conditional type in the return position which first checked if forKids
is true
in the original type. If it was, then it returned the function - otherwise it returned never
. Returning never here would mean that the function would not exist - removing them from the mapping process.
The community came up with quite a few alternative takes which provided type safety inside the functions and used different routes like removing the non-kids movie keys ahead of time.
Share Your Experience
We would love to hear your feedback on this weeks challenges whether good or a bad! If you can please take our quick 4 question survey that can be found here
Want More?!
If you want to learn more about TypeScript, check out some of our best resources:
Happy Typing :)
Posted on October 31, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.