
TypeScript에서
extends는 두 가지 문맥에서 사용됩니다.
1) 제네릭 제약(Constraint)
2) 조건부 타입(Conditional Type)
겉보기엔 똑같아 보여도, 의미와 쓰임새는 전혀 다릅니다. 이번 글에서는 이 둘을 확실히 구분해보고, 마지막에 실전 예제까지 살펴봅니다.
제네릭 파라미터 T가 어떤 타입 U의 부분집합이어야 함을 선언하는 것.
형태: <T extends U>
→ “T는 반드시 U에 호환 가능한 타입이어야 한다.”
function lengthOf<T extends { length: number }>(arg: T): number {
return arg.length;
}
lengthOf("hello"); // string → length 있음 → OK
lengthOf([1, 2, 3]); // number[] → length 있음 → OK
lengthOf({ length: 10 }); // 객체도 OK
// lengthOf(123); // ❌ number에는 length 없음
👉 T extends { length: number } 제약이 걸렸기 때문에, length 속성이 없는 타입은 아예 함수 호출이 불가능합니다.
조건문처럼 타입을 분기합니다.
형태: T extends U ? X : Y
→ “T가 U에 할당 가능하면 X, 아니면 Y”
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"
type ElementType<T> = T extends (infer U)[] ? U : T;
type A = ElementType<string[]>; // string
type B = ElementType<number>; // number
👉 infer 키워드를 함께 써서, 조건이 맞으면 내부 타입을 변수처럼 추출해내는 것도 가능합니다.
실무에서 자주 쓰는 패턴은 두 가지가 결합된 형태입니다.
function getProp<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
| 문맥 | 문법 | 의미 | 예시 |
|---|---|---|---|
| 제약(Constraint) | <T extends U> | 제네릭 선언 시 제한. T는 반드시 U 안에 있어야 한다. | <T extends object> |
| 조건부 타입(Conditional Type) | T extends U ? X : Y | 타입 계산 시 분기. T가 U에 할당 가능하면 X, 아니면 Y. | T extends string ? "ok" : "no" |
제약으로 안전성 확보
function pick<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
→ K가 반드시 T의 키라는 제약이 있으므로 안전합니다.
조건부 타입으로 타입 변환
type NonNullable<T> = T extends null | undefined ? never : T;
type A = NonNullable<string | null>; // string
type B = NonNullable<number | undefined>; // number
Day4 문제인 api-endpoints를 예로 들어보겠습니다.
export type ApiMap = {
"/login": {
req: { id: string; pw: string };
res: { token: string };
};
"/me": {
res: { id: string; name: string };
};
"/items": {
req: { page: number };
res: { items: Array<{ id: string; name: string }> };
};
};
export type RequestOf<M, K extends keyof M> =
"req" extends keyof M[K] ? M[K]["req"] : never;
export type ResponseOf<M, K extends keyof M> =
M[K]["res"];
여기서 쓰이는 extends 두 가지 문맥:
제약
<K extends keyof M> 조건부 타입
"req" extends keyof M[K] ? ... : ... 👉 이 조합 덕분에, "/login" 같은 엔드포인트에서는 요청 타입이 나오고, "/me" 같은 요청 없는 엔드포인트에서는 자동으로 never가 됩니다.