TypeScriptのループと、ループ変数に付く型
ホーム
TypeScriptのループ
TypeScriptのループ:for...of、for...in、forEachと型 | We.Inc
for、for...of、for...in、forEachでTypeScriptがループ変数に付ける型、for...inの中でobj[key]が失敗する理由、そしてオブジェクトやMapを安全にループする方法を紹介します。
TypeScriptのループはJavaScriptと同じで、for、for...of、for...in、while、do...while、そして配列メソッドのforEachです。TypeScriptが加えるのは、各ループ変数の型です。for...ofは要素の型を、for...inは常に文字列のキーを与え、forEachはコールバックに要素の型を渡しますが、途中で抜けることはできません。
ループ変数の型
for (let i = 0; i < a.length; i++) number[] i: number
for (const x of a) number[] x: number
for (const k in obj) { tea: number; cake: number } k: string
for (const i in a) number[] i: string(数値ではない)
for (const [k, v] of Object.entries(obj)) { tea: number; cake: number } k: string, v: number
for (const [k, v] of map) Map<string, number> k: string, v: number
a.forEach((x) => ...) [string, number] のタプル x: string | number
シナリオを選ぶと、コード、コンパイラーがループ変数に割り当てる型、そして実行したときにループが出力する内容が表示されます。そのうち2つはわざとコンパイルエラーにしています。TypeScriptで最もつまずきやすいループだからです。
コードの上にあるボタンからシナリオを選びます。 型の行を読んで、コンパイラーがループ変数をどう認識しているかを確認します。 出力欄で、ループが出力する内容、またはコンパイルできない場合のtscの正確なエラーを確認します。 コードをコピーして、自分のプロジェクトかTypeScriptプレイグラウンドに貼り付けます。
配列、文字列、Map、Setには、for...ofを優先しましょう。キャストなしで変数に要素の型が付き、ブレークやコンティニューも使えます。 値が必要なときは、Object.entriesでオブジェクトをループしましょう。型付きのペアが得られ、for...inで起きるインデックスのエラーを避けられます。 固定のリストにas constを付けると、ループ変数はそのリテラル値のユニオン型になり、スイッチ文と相性がよくなります。 forEachに非同期のコールバックを渡しても、待ってはくれません。中で待機するならfor...ofを使うか、マップした結果に対してPromise.allを使いましょう。
よくある質問
for...inループのキーはなぜ文字列なのですか?
for...inは、プロトタイプチェーンから継承したものも含めて列挙可能な文字列キーを巡回します。また、実行時にオブジェクトに余分なキーがないとTypeScriptは断言できません。キーをkeyof Tとして型付けするのは言語が守れない約束になるため、文字列のままなのです。
"Element implicitly has an 'any' type because expression of type 'string' can't be used to index" はどう直せばよいですか?
Object.entries(obj) に切り替えてキーと値を一緒に取得するか、キーを一度だけキャストします:for (const k of Object.keys(obj) as (keyof typeof obj)[])。キャストするのは、オブジェクトに余分なキーがないとわかっている場合だけにしましょう。
TypeScriptでforEachから途中で抜けられますか?
いいえ。JavaScriptと同じく、forEachが止まるのはコールバックが例外を投げたときだけです。ブレークを使えるfor...ofか、最初に一致した時点で止まるsome() やfind() を使いましょう。
TypeScriptでfor...ofはMapに使えますか?
はい、ターゲットがES2015以降なら使えます。各周回で、Mapのジェネリクスから型付けされたキーと値のペアが得られます。古いターゲットでは、downlevelIterationを有効にしてください。
従来のforループのカウンターにはどんな型が付きますか?
let i = 0 は数値型と推論され、a[i] は要素の型になります。noUncheckedIndexedAccessを有効にすると、a[i] は要素の型またはundefinedになります。より厳格ですが、実態により近い型です。
ループはJavaScript、新しいのは型の部分
TypeScriptはループの構文を何も追加しません。このページのすべてのループは同じJavaScriptにコンパイルされます。各ループが実行時に何をするかは、このサイトのJavaScriptループのガイドで解説しています。ここで注目するのはコンパイラーで、各ループ変数にどんな型が付くか、そしてそこから生じるエラーです。 ほとんどの場合、型推論が仕事をしてくれます。ループ変数に型注釈を書くことはめったになく、書きたくなったときは、たいていコレクション自体にもっと良い型が必要だというサインです。
エラーなしでオブジェクトをループする
厄介なのはオブジェクトです。for...inもObject.keysもただの文字列を返し、strictモードでは型付きのオブジェクトをただの文字列でインデックスするとエラーになります。Object.entriesはキーと値のペアを返すことでこれを避け、値の型はオブジェクトの値から決まります。 そのオブジェクトが頻繁にループする参照テーブルなら、Record<string, V> として型付けするか、Mapに格納することを検討しましょう。どちらもループ変数の型が明確になり、エラーは消えます。
ループ内での絞り込み
(string | number)[] のようなユニオン型の配列をループすると、変数もユニオン型になります。本体の中でtypeofを使って絞り込めば、その分岐の残りの部分ではコンパイラーが絞り込んだ型を追跡します。 絞り込みはループの周回をまたいだり、後で実行されるコールバックの中まで引き継がれたりはしないので、値を使う場所で改めてチェックしましょう。
ループと非同期コード
for...ofで1つずつ待機する
各リクエストは前のリクエストを待ちます。結果の配列は関数の戻り値の型から型付けされます。
Promise.allでまとめて実行する
マップ処理ですべてのリクエストを一度に開始し、Promise.allがその全部を待ちます。結果の順序はIDの順序と一致します。
for...ofで配列
s: number
number[] の要素の型が、そのままループ変数に引き継がれます。
for...inでオブジェクト
k: string
キーはただの文字列なので、それで型付きオブジェクトをインデックスすると、strictモードではエラーになります。
Object.entries
name: string, price: number
ここではObject.entriesが [string, number][] を返すので、ペアの両方に型が付きます。
keyofとしてのキー
k: "tea" | "cake"
一度だけのキャストで、キーが宣言したものと完全に一致するとコンパイラーに伝えます。オブジェクトが余分なキーを持ち得ない場合にだけ安全です。
for...inで配列
i: string
for...inでは配列のインデックスが文字列として届くため、数値型の変数に代入するとエラーになります。
for...ofでMap
item: string, qty: number
エントリーを分割代入すると、両方のジェネリック型が得られます。順序はエントリーが最初に追加された順です。
forEachでタプル
x: string | number
forEachはタプルを要素の型のユニオン型の配列として扱うので、typeofで絞り込みましょう。
as constのリスト
as constでリテラル値が保たれるので、ループ変数は網羅的にスイッチできるユニオン型になります。
ループ変数にはどんな型が付く?
ループのシナリオを選ぶ
型:
出力
tscのコンパイルエラー
無料で始める · クレジットカード不要
関連: ガイド|Webマーケティングと集客を学ぶ
製品
ご利用いただける方
機能
リソース
会社情報
サイトマップを見る