Advanced TypeScript | Conditional Types· Template Literals
이 글의 핵심
Advanced TypeScript: conditional types, infer, template literals, mapped types, key remapping—type-safe events, reducers, and query objects.
Introduction
TypeScript’s advanced type system lets you write precise, type-safe code that scales with your domain.
1. Conditional types
Basic syntax
// T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
type C = IsString<"hello">; // true
Practical checks
// Is it an array?
type IsArray<T> = T extends any[] ? true : false;
type D = IsArray<string[]>; // true
type E = IsArray<number>; // false
// Is it a function?
type IsFunction<T> = T extends (...args: any[]) => any ? true : false;
type F = IsFunction<() => void>; // true
type G = IsFunction<string>; // false
Nested conditionals
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type T1 = TypeName<string>; // "string"
type T2 = TypeName<42>; // "number"
type T3 = TypeName<() => void>; // "function"
2. The infer keyword
Concept
infer introduces a type variable inside a conditional type.
// Element type of an array
type ElementType<T> = T extends (infer U)[] ? U : never;
type H = ElementType<string[]>; // string
type I = ElementType<number[]>; // number
type J = ElementType<string>; // never
Inferring return types
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { id: "U001", name: "홍길동" };
}
type User = MyReturnType<typeof getUser>;
// { id: string; name: string; }
function getNumber(): number {
return 42;
}
type Num = MyReturnType<typeof getNumber>; // number
Inferring parameter types
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
function createUser(name: string, age: number, email: string) {
return { name, age, email };
}
type Params = MyParameters<typeof createUser>;
// [string, number, string]
// First parameter only
type FirstParam<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
type First = FirstParam<typeof createUser>; // string
Unwrapping Promise
type Awaited<T> = T extends Promise<infer U> ? U : T;
type K = Awaited<Promise<string>>; // string
type L = Awaited<Promise<number>>; // number
type M = Awaited<string>; // string
3. Template literal types
Basics
type EventName = "click" | "focus" | "blur";
type EventHandler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"
interface Events {
onClick: () => void;
onFocus: () => void;
onBlur: () => void;
}
String built-ins
type Greeting = "hello world";
type Upper = Uppercase<Greeting>; // "HELLO WORLD"
type Lower = Lowercase<Greeting>; // "hello world"
type Cap = Capitalize<Greeting>; // "Hello world"
type Uncap = Uncapitalize<Greeting>; // "hello world"
API shapes
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Resource = "users" | "posts" | "comments";
type ApiEndpoint = `/${Resource}`;
// "/users" | "/posts" | "/comments"
type ApiRoute = `${HttpMethod} ${ApiEndpoint}`;
// "GET /users" | "POST /users" | ...
type IdEndpoint = `${ApiEndpoint}/${string}`;
// "/users/:id" | "/posts/:id" | ...
4. Mapped types
Basic mapping
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
type MyRequired<T> = {
[K in keyof T]-?: T[K];
};
interface User {
name: string;
age?: number;
}
type ReadonlyUser = MyReadonly<User>;
type PartialUser = MyPartial<User>;
type RequiredUser = MyRequired<User>;
Key remapping
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface User {
name: string;
age: number;
}
type UserGetters = Getters<User>;
// {
// getName: () => string;
// getAge: () => number;
// }
Filtering keys by value type
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface User {
id: string;
name: string;
age: number;
isActive: boolean;
}
type StringProps = PickByType<User, string>;
// { id: string; name: string; }
type NumberProps = PickByType<User, number>;
// { age: number; }
5. Practical examples
Example 1: Type-safe event bus
type EventMap = {
"user:login": { userId: string; timestamp: Date };
"user:logout": { userId: string };
"post:create": { postId: string; title: string };
"post:delete": { postId: string };
};
class EventEmitter<T extends Record<string, any>> {
private listeners: {
[K in keyof T]?: Array<(data: T[K]) => void>;
} = {};
on<K extends keyof T>(event: K, listener: (data: T[K]) => void) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event]!.push(listener);
}
emit<K extends keyof T>(event: K, data: T[K]) {
const listeners = this.listeners[event];
if (listeners) {
listeners.forEach(listener => listener(data));
}
}
}
const emitter = new EventEmitter<EventMap>();
emitter.on("user:login", (data) => {
console.log(`사용자 로그인: ${data.userId}`);
});
emitter.emit("user:login", {
userId: "U001",
timestamp: new Date()
});
Example 2: Typed actions for state
type State = {
user: { name: string; email: string } | null;
posts: Array<{ id: string; title: string }>;
loading: boolean;
};
type Action<T extends keyof State> = {
type: `SET_${Uppercase<string & T>}`;
payload: State[T];
};
type Actions = {
[K in keyof State]: Action<K>;
}[keyof State];
function reducer(state: State, action: Actions): State {
switch (action.type) {
case "SET_USER":
return { ...state, user: action.payload };
case "SET_POSTS":
return { ...state, posts: action.payload };
case "SET_LOADING":
return { ...state, loading: action.payload };
default:
return state;
}
}
const initialState: State = {
user: null,
posts: [],
loading: false
};
const newState = reducer(initialState, {
type: "SET_USER",
payload: { name: "홍길동", email: "hong@test.com" }
});
Example 3: Type-safe query object
type QueryOperator = "eq" | "ne" | "gt" | "lt" | "gte" | "lte";
type Query<T> = {
[K in keyof T]?: {
[O in QueryOperator]?: T[K];
};
};
interface User {
id: string;
name: string;
age: number;
email: string;
}
function find<T>(query: Query<T>): T[] {
return [];
}
const users = find<User>({
age: { gte: 20, lt: 30 },
name: { eq: "홍길동" }
});
6. Advanced utility patterns
DeepPartial
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
interface User {
id: string;
profile: {
name: string;
address: {
city: string;
zipCode: string;
};
};
}
const update: DeepPartial<User> = {
profile: {
address: {
city: "서울"
}
}
};
DeepReadonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
const user: DeepReadonly<User> = {
id: "U001",
profile: {
name: "홍길동",
address: {
city: "서울",
zipCode: "12345"
}
}
};
Summary
Takeaways
- Conditional types:
T extends U ? X : Y - infer: infer types inside branches (functions, arrays,
Promise) - Template literals: compose string literal types
- Mapped types: transform object types
- Key remapping:
asin mapped types
Where this shows up
- Type-safe event APIs
- Discriminated unions and reducers
- Query builders and ORM-style filters
- Custom utilities beyond the standard library
Next steps
Related posts
- Kotlin advanced features | DSL, reflection, annotations
- Python decorators | @decorator explained
- TypeScript getting started | install, config, syntax
- Advanced TypeScript types | unions, intersections, literals
- TypeScript interfaces | complete guide
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Advanced TypeScript: conditional types, infer, template literals, mapped types, key remapping—type-safe events, reducers…
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- TypeScript Decorators
- TypeScript REST API Project | Express· Layered Architecture
- TypeScript Utility Types | Partial· Pick
Keywords Covered in This Article (Related Search Terms)
This article covers TypeScript, Conditional Types, Template Literal Types, infer, Mapped Types.