타입 매개변수가 여러 개일 때 벌어지는 일
매개변수가 늘어나면 관계를 명시해야 한다
앞 챕터의 pickLarger<T>(a: T, b: T)는 두 값이 "같은 타입이어야 한다"는 관계를 T 하나로 표현했습니다. 하지만 두 값이 서로 다른 타입이면서도 연관되어 있는 경우가 실무에서는 더 흔합니다. 이럴 때 타입 매개변수를 여러 개 씁니다.
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
const merged = merge({ id: 1 }, { name: "eun" });
// merged: { id: number } & { name: string }T와 U는 서로 다른 타입이 되어도 상관없다는 것을 표현하기 위해 매개변수를 분리했습니다. 반환 타입 T & U는 두 타입을 합친 교차 타입(intersection type)으로, merged는 id와 name을 모두 가진 객체로 정확히 추론됩니다. T와 U를 하나로 합쳤다면(merge<T>(a: T, b: T)) 두 인수가 같은 타입이어야 한다는 잘못된 제약이 생겼을 겁니다.
순서가 있는 매핑: 키와 값의 관계
타입 매개변수 두 개가 서로 독립적이지 않고, 하나가 다른 하나를 제한하는 경우도 자주 나옵니다. 이벤트 시스템이 대표적입니다.
type EventMap = {
click: { x: number; y: number };
keydown: { key: string };
};
function on<K extends keyof EventMap>(
event: K,
handler: (payload: EventMap[K]) => void
) {
// ...
}
on("click", (payload) => {
console.log(payload.x, payload.y); // payload는 { x: number; y: number }로 추론
});
on("keydown", (payload) => {
console.log(payload.key); // payload는 { key: string }로 추론
});
on("click", (payload) => {
console.log(payload.key); // 에러: 'key' 속성이 없다
});여기서 타입 매개변수는 K 하나뿐이지만, handler의 매개변수 타입이 EventMap[K]로 K에 종속되어 있습니다. event로 "click"을 넘기면 K가 "click"으로 고정되고, 그 즉시 handler가 받아야 할 payload의 타입도 { x: number; y: number }로 결정됩니다. 이런 식으로 하나의 인수가 나머지 인수들의 타입을 "잠그는" 패턴을 이벤트 핸들러, 리듀서, 폼 라이브러리 등에서 자주 보게 됩니다.
흔한 실수: 관계없는 값에 억지로 같은 이름을 붙이기
타입 매개변수를 여러 개 쓸 때 실무에서 자주 보는 실수는, 관계가 있는지 없는지 따지지 않고 습관적으로 T, U, V를 늘어놓는 것입니다.
// U가 반환 타입에도, 다른 매개변수에도 등장하지 않는다
function logAndReturn<T, U>(value: T, label: U): T {
console.log(label, value);
return value;
}U는 label의 타입을 표시하는 것 말고는 아무 역할이 없습니다. label이 어떤 타입이든 함수의 동작이나 반환 타입에 영향을 주지 않으니, 굳이 제네릭으로 선언할 이유가 없습니다.
function logAndReturn<T>(value: T, label: string): T {
console.log(label, value);
return value;
}이렇게 바꿔도 기능은 동일합니다. 타입 매개변수를 추가하기 전에 "이 매개변수가 반환 타입이나 다른 매개변수의 타입을 결정하는가"를 확인하는 습관을 들이면, 필요 없는 제네릭을 걸러낼 수 있습니다. 이 판단 기준은 마지막 챕터 "제네릭을 쓰지 말아야 할 때"에서 더 넓은 사례로 다시 다룹니다.
타입 매개변수가 하나에서 여러 개로 늘어나는 순간부터 설계 감각이 요구됩니다. 각 매개변수가 서로 독립적인지, 하나가 다른 것을 제한하는지, 아니면 애초에 필요가 없는지 — 이 세 가지를 구분하는 게 다중 제네릭을 다루는 핵심입니다.