---
id: typescript-generic-constraints
title: Generic Constraints — 의미 있는 제네릭 작성
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [typescript, generics, constraints, vibe-coding]
tech_stack: { language: "TypeScript 5+", applicable_to: ["Web", "Backend"] }
applied_in: []
aliases: [extends keyword, type parameter, conditional types]
---

# Generic Constraints

> 제네릭 `T` 그대로 쓰면 "어떤 것이든" 의미밖에 없음. **`T extends X` 로 제약**해야 컴파일러가 의미 있는 검증 가능. 제약 없는 제네릭은 보통 잘못된 추상화 신호.

## 📖 핵심 개념
- `<T>`: any-shape. T 의 어떤 속성도 접근 불가.
- `<T extends Shape>`: T 가 Shape 호환. T 의 Shape 멤버 접근 가능.
- `<K extends keyof T>`: K 는 T 의 키. 안전한 lookup.

## 💻 코드 패턴

### 1. keyof 제약 — 타입 안전 lookup
```ts
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  return keys.reduce((acc, k) => ({ ...acc, [k]: obj[k] }), {} as Pick<T, K>);
}

pick({ a: 1, b: 2, c: 3 }, ['a', 'c']); // ✅ { a, c }
pick({ a: 1 }, ['z']); // ❌ 'z' 는 keyof T 아님
```

### 2. 구조 제약
```ts
interface HasId { id: string }

function findById<T extends HasId>(arr: T[], id: string): T | undefined {
  return arr.find(x => x.id === id);
}
```

### 3. Conditional types — 타입 변환
```ts
type ExtractArrayItem<T> = T extends (infer U)[] ? U : never;

type A = ExtractArrayItem<string[]>;  // string
type B = ExtractArrayItem<number>;    // never (배열 아님)
```

### 4. Default + constraint
```ts
function paginate<T = unknown>(items: T[], pageSize: number = 20): T[][] { ... }
```

### 5. Multiple constraints (intersection)
```ts
function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}
```

## 🤔 의사결정 기준
| 상황 | 제약 |
|---|---|
| 함수가 입력 그대로 통과 | `<T>` (제약 없음 OK) |
| 키 lookup | `<K extends keyof T>` |
| 특정 메서드/필드 접근 | `<T extends Interface>` |
| 배열 / 함수 / promise 안 unwrap | conditional + infer |
| 매우 유연한 helper | 보통 제네릭 1~2개 |
| 제네릭 4개 이상 | 추상화 잘못. 분리 검토 |

## ❌ 안티패턴
- **`<T>` 만 있고 본문에서 `as any` 캐스팅**: 제네릭 의미 없음. 정확한 제약을 줘서 안전하게.
- **사용자가 매번 explicit type argument 줘야 함** (`func<MyType>(...)`): 추론 안 되는 시그니처. 인자에서 추론되도록 재설계.
- **거대 conditional 체인 (5+ ternary)**: 디버그 지옥. 단계별 named type 으로 분리.
- **`extends any`**: 제약 0. 그냥 `T`.
- **제네릭으로 unknown 치환**: 호출자가 cast 해야 함. 진짜 제네릭이 필요한지 재고.

## 🤖 LLM 활용 힌트
- "제네릭 도입 시 항상 `extends ...` 제약 명시. 제약 없으면 본문에서 의미 있는 작업 불가" 강조.
- LLM 이 `T = any` 디폴트 주면 제거 요청.

## 🔗 관련 문서
- [[TypeScript_Type_Predicates]]
- [[TypeScript_Const_Assertions]]
