[2026] Advanced TypeScript | Conditional Types, Template Literals, infer

[2026] Advanced TypeScript | Conditional Types, Template Literals, infer

이 글의 핵심

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

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 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

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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. 아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 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

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

type EventName = "click" | "focus" | "blur";
type EventHandler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"
interface Events {
    onClick: () => void;
    onFocus: () => void;
    onBlur: () => void;
}

String built-ins

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

  1. Conditional types: T extends U ? X : Y
  2. infer: infer types inside branches (functions, arrays, Promise)
  3. Template literals: compose string literal types
  4. Mapped types: transform object types
  5. Key remapping: as in 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


... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3