DAY 30 OF 30
Template literal types
Yesterday, in one sentence
infer do?A conditional type chooses between two types based on a check, written
T extends U ? X : Y, where extends checks whether T is assignable to U.
infer declares a new type variable by inferring its type from the one being
checked, and can only be used inside the extends clause of a conditional type.
It is useful for typing data that may change or comes from a third party library,
where you would rather derive the type than write it out by hand.
Did you say:
- ✓chooses between two types based on a check
- ✓written `T extends U ? X : Y`
- ✓`infer` declares a variable by inferring its type
- ✓useful for types that may change or come from a third party
Interview question
A type that builds an object type by looping over a union of keys and giving each
key a type, written [K in keyof T]. You can add or remove ? and readonly as
you go. The built in utility types like Partial and Readonly are written this
way.
Did you say:
- ✓builds an object type by looping over a union of keys
- ✓written `[K in keyof T]`
- ✓can add or remove `?` and `readonly` as it goes
- ✓the built in utility types are written this way
A template literal type builds a string literal typeLiteral Type out of other types, using the same backtick syntax as a JavaScript template literal.
type Alignment = "hero" | "villain";
type Label = `marvel-${Alignment}`;
// "marvel-hero" | "marvel-villain"
Interpolating a unionUnion Type produces every combination, so the result is a union too. With two unions you get all the pairs.
type Power = "flight" | "healing";
type Level = "basic" | "max";
type Upgrade = `${Power}-${Level}`;
// "flight-basic" | "flight-max" | "healing-basic" | "healing-max"
The real use is renaming keys in a mapped typeMapped Type. Say we
have a Hero type and we want an event handler type derived from it.
Three things get us there.
First, loop over the keys with [K in keyof T].
Second, give each generated key a new name with an as clause, which is where the
template literal goes.
[K in keyof T as `on${K}`]
That does not compile. keyof T can be a string, a number or a symbol, and only
strings are allowed in a template literal, so we have to make sure it is a string. An intersection typeIntersection Type does that.
[K in keyof T as `on${string & K}`]
Third, attack needs to be onAttack. We have already added the on, but the key
itself still needs capitalising. Capitalize is a built in type that does exactly
that, and TypeScript has three others like it: Uppercase, Lowercase and
Uncapitalize.
Put together we get this.
// map over each key of T, renaming it to on<Key>
type EventHandlers<T> = {
[K in keyof T as `on${Capitalize<string & K>}`]: () => string;
};
type Hero = {
attack: string;
heal: string;
};
type HeroEvents = EventHandlers<Hero>;
// { onAttack: () => string; onHeal: () => string }
Check yourself
Route here?
type Section = "heroes" | "villains"
type Route = `/${Section}/list`
Interpolating a union into a template literal type produces one string literal per member, so the result is a union of both routes.
Types that build string literal types using backtick syntax, the same as a
JavaScript template literal but with types interpolated instead of values.
Interpolating a union produces every combination. They are often used with an
as clause in a mapped type to rename keys.
Did you say:
- ✓build string literal types with backtick syntax
- ✓types are interpolated instead of values
- ✓a union produces every combination
- ✓used with an `as` clause in a mapped type to rename keys