Menu

TypeScriptのMap: Map<K, V>、get・set・has とループ

TypeScriptで Map を使う方法。型付きの Map<K, V> の作り方、get が V | undefined を返す理由、set・has・delete、挿入順でのループ、オブジェクトのキー、オブジェクトや JSON との相互変換、Map とオブジェクトと Record の違い、array.map() の型を解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

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] のタプルの配列か、それを返すほかの反復可能なものです。

推論の落とし穴が2つあります:

  • 型引数もエントリーもない 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 の結果を直接チェックするほうが短く、安全です。非 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すべて削除
sizenumberメソッドではなくプロパティ
keys(), values()K、V のイテレーター配列にスプレッド: [...map.keys()]
entries(), for...of[K, V] のイテレーター挿入順
forEach((value, key) => ...)void注意: 値が先

Map をループする

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.sizeObject.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 のエントリーはプロパティではないので、Map そのものに JSON.stringify(map) を使うと "{}" になります。逆の変換は new Map(Object.entries(obj)) です。

TypeScriptで array.map のコールバックに型を付けるには?

たいていは必要ありません。items.map((item) => item.name) では、item の型は配列から、結果の型はコールバックの戻り値から推論されます。結果の型を指定したい場合は、items.map<string>(...) のように型引数で渡すか、コールバックの戻り値に型注釈を書きます。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める