TypeScript의 Map은 키와 값에 타입이 붙은 JavaScript 내장 Map입니다. Map<string, number>는 문자열을 숫자에 매핑합니다. new Map<K, V>()로 만든 뒤 set, get, has, delete를 씁니다. 키가 없을 수 있으므로 get은 V | undefined를 반환합니다.
TypeScript에서 Map을 유용하게 만드는 것은 타입 인수입니다. 모든 set이 검사되고 모든 get이 값 타입을 반환합니다. 마지막 줄은 컴파일 오류(TS2345)이며, @ts-expect-error 덕분에 블록이 계속 실행됩니다.
이 페이지는 Map 컬렉션을 다룹니다. array.map()을 찾아왔다면 마지막 절을 보세요.
Map 만들기
타입은 타입 인수, 초기 항목, 또는 타입 표기에서 옵니다. 초기 항목은 [key, value] 튜플의 배열이거나, 그런 튜플을 내놓는 다른 무엇이든 됩니다.
추론에는 함정이 두 가지 있습니다.
- 타입 인수도 항목도 없는
new Map()은Map<any, any>입니다. 넣고 꺼내는 어떤 것도 검사되지 않습니다. 항상new Map<K, V>()로 쓰세요. - 값 타입이 서로 다른 항목에서는 유니언이 추론되지 않습니다.
new Map([["a", 1], ["b", "x"]])는 TS2769 오류(No overload matches this call)입니다. 타입을 직접 쓰세요:new Map<string, number | string>([...]).
get은 V | undefined를 반환한다
Map은 키가 존재한다고 보장할 수 없으므로 get의 타입은 V | undefined입니다. strict에서는 값을 V로 쓰기 전에 undefined를 처리해야 합니다.
index.ts(4,7): error TS2322: Type 'number | undefined' is not assignable to type 'number'.
Type 'undefined' is not assignable to type 'number'.
해결 방법을 흔히 쓰는 순서대로 보면 다음과 같습니다.
TypeScript는 나중에 get을 호출할 때 has가 true를 반환했다는 것을 기억하지 않습니다. get의 결과를 직접 검사하는 편이 더 짧고 안전합니다. non-null 단언 stock.get("apples")!는 오류를 없애지만 키가 없을 때 아무런 보호도 해 주지 않습니다.
Map 메서드와 타입
| 멤버 | Map<K, V>에서의 타입 | 설명 |
|---|---|---|
new Map<K, V>(entries?) | Map<K, V> | entries: [K, V]의 이터러블 |
set(key, value) | this | 추가하거나 교체, 체이닝 가능 |
get(key) | V | undefined | 없으면 undefined |
has(key) | boolean | |
delete(key) | boolean | 무언가 삭제됐으면 true |
clear() | void | 모두 삭제 |
size | number | 메서드가 아니라 프로퍼티 |
keys(), values() | K, V의 이터레이터 | 배열로 스프레드: [...map.keys()] |
entries(), for...of | [K, V]의 이터레이터 | 삽입 순서 |
forEach((value, key) => ...) | void | 주의: 값이 먼저 옵니다 |
Map 순회하기
Map은 삽입 순서대로 순회합니다. 맵을 for...of로 돌면 [K, V] 타입의 [key, value] 튜플이 나옵니다.
이미 있는 키에 set하면 값은 바뀌지만 순서상의 원래 위치는 유지됩니다.
객체 키와 개수 세기
객체와 배열을 포함해 어떤 값이든 키가 될 수 있습니다. 키는 ===처럼 비교되므로 내용이 같은 두 객체도 서로 다른 키입니다. Map은 항목의 개수를 세거나 그룹으로 묶는 표준적인 방법이기도 합니다.
객체의 내용을 기준으로 키를 삼으려면 user.id나 `${x},${y}`처럼 문자열이나 숫자 키를 따로 만드세요.
Map과 객체와 Record 비교
Map<K, V> | 객체 / Record<string, V> | |
|---|---|---|
| 키 타입 | 무엇이든, ===처럼 비교 | string(숫자는 문자열이 됨), symbol |
| 없는 키의 타입 | get이 V | undefined 반환 | noUncheckedIndexedAccess가 꺼져 있으면 obj[key]는 V |
| 순서 | 삽입 순서 | 대체로 삽입 순서지만, 정수처럼 보이는 키가 오름차순으로 먼저 옴 |
| 크기 | map.size | Object.keys(obj).length |
| 잦은 추가와 삭제 | 최적화됨 | 최적화되지 않음 |
| JSON | 바로 안 됨(JSON.stringify(map)은 "{}") | 바로 됨 |
| 리터럴 문법, 구조 분해 | 아니요 | 예 |
| 실수로 상속된 키 | 없음 | "toString" in {}이 true |
경험칙은 이렇습니다. 키가 데이터(사용자 id, 단어, 캐시 항목)이고 런타임에 바뀌는 컬렉션에는 Map을, 알려진 키의 고정된 집합이나 JSON으로 오가는 데이터에는 객체 타입이나 Record를 씁니다. 문자열 키 조회에 쓰는 인덱스 시그니처, Record, Map의 비교는 딕셔너리 페이지에 있습니다.
Map을 객체와 JSON으로 변환하기
Map의 항목은 프로퍼티가 아니므로 JSON.stringify는 이를 보지 못합니다. Object.fromEntries와 Object.entries를 거쳐 변환하세요.
JSON.parse는 any를 반환하므로 as Record<...>로 데이터가 어떤 모양이어야 하는지 밝힙니다. 이는 런타임 검사가 아닙니다. 신뢰할 수 없는 JSON은 그 타입을 믿기 전에 검증하세요.
array.map()의 타입
"typescript map"을 검색하는 사람 중 상당수는 각 요소를 변환해 새 배열을 반환하는 배열 메서드를 찾습니다. 그 타입은 콜백에서 추론되므로 타입 표기가 필요한 경우는 드뭅니다.
콜백의 반환 타입을 표기하는 것((u): Option => ...)이 결과 타입을 밝히는 가장 명확한 방법입니다. 반환하는 객체에서 프로퍼티가 빠지거나 이름이 틀리면 콜백에서 바로 컴파일 오류가 납니다.
자주 묻는 질문
TypeScript에서 Map은 어떻게 만드나요?
생성자에 키와 값의 타입을 넘깁니다: const ages = new Map<string, number>(). 초기 항목이 있으면 타입이 추론됩니다: new Map([["ada", 36]])은 Map<string, number>입니다. 타입도 항목도 없는 new Map()은 Map<any, any>가 되어 검사가 꺼지므로 항상 타입을 지정하세요.
TypeScript에서 Map.get은 왜 undefined를 반환하나요?
키가 없을 수 있으므로 map.get(key)의 타입은 V | undefined입니다. TypeScript는 앞서 호출한 map.has(key)를 나중의 get과 연결하지 않으므로 has 뒤에도 undefined를 처리해야 합니다. 결과를 변수에 담아 검사하거나 ??로 기본값을 주세요.
TypeScript에서 Map과 객체의 차이는 무엇인가요?
Map은 모든 타입(객체 포함)의 키를 받고, 삽입 순서를 유지하며, size가 있고, 잦은 추가와 삭제에 맞게 만들어졌습니다. 일반 객체나 Record<string, V>는 문자열(과 심볼) 키만 가지며, JSON으로 바로 직렬화되고, 리터럴 문법과 구조 분해를 지원합니다. 동적인 키를 가진 컬렉션에는 Map을, 고정된 형태와 JSON 데이터에는 객체를 쓰세요.
TypeScript에서 Map을 객체나 JSON으로 변환하려면 어떻게 하나요?
Object.fromEntries(map)은 Map<string, V>를 일반 객체로 바꾸고, 이를 JSON.stringify로 직렬화할 수 있습니다. Map 자체에 JSON.stringify(map)을 쓰면 "{}"가 나오는데, Map의 항목은 프로퍼티가 아니기 때문입니다. 반대로 변환하려면 new Map(Object.entries(obj))를 씁니다.
TypeScript에서 array.map의 콜백 타입은 어떻게 지정하나요?
대개 지정할 필요가 없습니다. items.map((item) => item.name)은 배열에서 item의 타입을, 콜백이 반환하는 값에서 결과 타입을 추론합니다. 결과 타입을 강제하려면 items.map<string>(...)처럼 타입 인수로 넘기거나 콜백의 반환 타입을 표기하세요.