Template Literal and Mapped Types in TypeScript
Advanced types let you describe APIs precisely: event names, CSS tokens, route parameters. You won't write these every day, but they make library-style code much safer.
Template literal types
type Size = 'sm' | 'md' | 'lg';
type Side = 'top' | 'bottom';
type MarginClass = `m${Capitalize<Side>}-${Size}`;
// 'mTop-sm' | 'mTop-md' | 'mTop-lg' | 'mBottom-sm' | ...
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickHandler = EventName<'click'>; // 'onClick'
Mapped types
Loop over the keys of a type to build a new one:
type Flags<T> = { [K in keyof T]: boolean };
interface Features { darkMode: string; beta: number }
type FeatureFlags = Flags<Features>; // { darkMode: boolean; beta: boolean }
Key remapping with as
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<{ name: string; age: number }>;
// { getName: () => string; getAge: () => number }
Conditional types and infer
type ElementType<T> = T extends (infer U)[] ? U : T;
type A = ElementType<string[]>; // string
// Pull route params out of a path string
type Params<P extends string> =
P extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param | keyof Params<Rest>]: string }
: P extends `${string}:${infer Param}`
? { [K in Param]: string }
: {};
type R = Params<'/users/:userId/posts/:postId'>; // { userId: string; postId: string }
Use them wisely
- Clever types are hard for teammates to read. Prefer simple types in application code.
- Advanced types shine in shared utilities and libraries, where many callers benefit.
- Hover over types in your editor to check what they resolve to.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.