
회사 프로젝트를 하면서 Record 타입을 자주 마주치곤 했다.
예를 들어 수질 상태 패널 컴포넌트에서는 각 탭의 경고 여부나 차트 옵션을 관리하기 위해 이런 코드가 쓰이고 있었다.
// 탭별 경고 상태 private warnings: Partial<Record<QualityStatusPanelTab, boolean>> = { ClAI: false, ECAI: false, TUAI: false, pHAI: false, }; // 탭별 차트 옵션 private chartOptions: Partial<Record<QualityStatusPanelTab, EChartsOption>> = { TotalAI: {}, ClAI: {}, ECAI: {}, TUAI: {}, pHAI: {}, Quality: {}, };
겉으로 보기엔 단순히 객체를 정의한 것 같지만 사실 Record<K, T>는 키와 값의 관계를 명확하게 문서화하고 실수할 여지를 줄여주는 도구였다.
나는
같은 궁금증이 생겼고
그 생각을 정리하면서 이번 글을 쓰게 되었다.
Record<K, T>는 키(key)의 타입이 K이고 값(value)의 타입이 T인 타입을 생성함.
예시:
type Person = Record<'name' | 'age', string | number>;
const john: Person = {
name: 'John Doe',
age: 30,
};
결론부터 말하자면 그렇지 않다.
단순 인덱스 시그니처(Index Signature) 문법은 키(key) 자리에는 string, number, symbol 같은 일반적인 타입만 올 수 있고, 특정 유니온 타입을 직접 넣는 것은 허용되지 않는다.
Record<K, T>는 사실 Mapped Type을 더 사용하기 쉽게 만들어 놓은 유틸리티 타입(Utility Type)이다.
lib.es5.d.ts) /**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};keyof T)을 “순회”해 새 속성 집합을 만드는 타입-Level 변환 도구입니다.in, keyof, as(Key Remapping), readonly?, ?(optional) 같은 문법으로 정교하게 가공할 수 있습니다.맵드 타입은 자바스크립트의 map 함수를 타입에 적용했다고 보면 된다.
아래와 같은 형태의 문법을 사용해야 한다.
{ [ P in K ] : T}
{ [ P in K ]? : T}
{ readonly [ P in K ] : T}
{ readonly [ P in K ]? : T}
interface는 정적 속성 선언과 인덱스 시그니처({ [key: string]: T })만 허용합니다.Mapped Type 문법에서는 type 별칭 안에서만 가능하지 interface 본문에서는 허용되지 않습니다.(이 부분은 Record 또한 마찬가지)interface CarType {
sedan: number;
suv: number;
truck: number;
}
interface CarInfo {
brand: string;
price: number;
}
// 1) Mapped Type 그대로
const CarData: { [K in keyof CarType]: CarInfo[] } = { ... };
// 2) Record 활용 (더 깔끔)
const CarData: Record<keyof CarType, CarInfo[]> = { ... };
두 가지는 사실상 동등한 타입이지만
Record<K, T>는 표준 유틸리티 타입으로 바로 의도를 드러내기 때문에 팀/실무 코드에서 더 읽기 좋고 간결합니다.
선택적 속성 여부가 다르다:
Mapped Type은 작성 방식에 따라 속성을 선택적으로 만들 수도 있습니다.// 선택적 속성 적용 예시
type OptionalCarData = { [K in keyof CarType]?: CarInfo[] };
// => sedan?: CarInfo[], suv?: CarInfo[], truck?: CarInfo[]Record<K, T>는 단순히type Record<K extends keyof any, T> = { [P in K]: T };즉,
Record<keyof CarType, CarInfo[]>
→ sedan, suv, truck 모두 필수
{ [K in keyof CarType]?: CarInfo[] }
// =>sedan?: CarInfo[], suv?: CarInfo[], truck?: CarInfo[]
→ sedan, suv, truck 모두 선택적
interface에서는 Mapped Type 문법을 직접 쓸 수 없으므로Record<...>를 쓰는 게 가장 깔끔합니다.Mapped Type은 옵션 속성 여부나 고급 변형을 제어할 수 있고Record는 단순 딕셔너리 구조만 표현하기 때문에