• 作成日:

TypeScriptでオブジェクトをループする方法 - 4つのループ処理を紹介

TypeScriptでオブジェクトをループする方法 - 4つのループ処理を紹介

今回はTypeScripでオブジェクトをループする方法を説明します。ループ自体はJavaScriptと変わりがありません。

説明する環境は以下です。

  • macOS Catalina v10.15.5
  • Visual Studio Code v1.57.0
この記事の目次

TypeScriptでオブジェクトをループする方法

オブジェクトのループは以下の4つがあります。

  • for of とObject.keysの組み合わせ
  • forEachとObject.keysの組み合わせ
  • mapとObject.keysの組み合わせ
  • for in

それぞれのループについては、以下のオブジェクトを使って説明していきます。

const vegetable = {
  パプリカ: '赤',
  小松菜: '緑',
  なすび: '紫',
  トマト: '赤',
};

まずこのオブジェクトをループする前に、interfaceまたはtypeエイリアスでこのオブジェクトの型を指定します。

今回はinterfaceで型を指定します。

/*-- 型を指定 --*/
interface Vegetables {
  パプリカ: string;
  小松菜: string;
  なすび: string;
  トマト: string;
}
/*-- オブジェクト --*/
const vegetable: Vegetables = {
  パプリカ: '赤',
  小松菜: '緑',
  なすび: '紫',
  トマト: '赤',
};

interfaceとtypeエイリアスのどちらで指定すれば良いかは以下の記事を参考にしてみてください。

for of とObject.keysの組み合わせでループ処理

Object.keysによってキーのみの配列を作り、キーごとにループさせています。ただ、以下のように書くと[key]の部分でエラーが表示されます。(暗黙的にanyを使っている場合はエラーにする設定)

/*-- エラーになる例 --*/

/*-- 型を指定 --*/
interface Vegetables {
  パプリカ: string;
  小松菜: string;
  なすび: string;
  トマト: string;
}
/*-- オブジェクト --*/
const vegetable: Vegetables = {
  パプリカ: '赤',
  小松菜: '緑',
  なすび: '紫',
  トマト: '赤',
};

//interfaceのVegetablesに[key]に型指定がないため、エラーになる!
for (const key of Object.keys(vegetable)) {
   console.log(`キーは${key}、値は${vegetable[key]}`);
 }

[key]を使ってvegetableにある値を取得しようとしていますが、interfaceのVegetablesには[key]に対して型指定ができていない、、とのこと。

正しく設定するには、以下のように[key]に対しても型を指定する必要があります(9行目)
注意点は、[key]の値をstringに設定した場合、その他のプロパティの値もすべてstringしないとエラーになります。1つだけnumberなど違う型は設定できません。

/*-- 正しくループできる例 --*/

/*-- 型を指定 --*/
interface Vegetables {
  パプリカ: string;
  小松菜: string;
  なすび: string;
  トマト: string;
  [key: string]: string; //[key]の型を指定する
}

/*-- オブジェクト --*/
const vegetable: Vegetables = {
  パプリカ: '赤',
  小松菜: '緑',
  なすび: '紫',
  トマト: '赤',
};

/*-- ループ --*/
for (const key of Object.keys(vegetable)) {
   console.log(`キーは${key}、値は${vegetable[key]}`);
 }

/*-- 以下はconsole.logで表示されたもの 
キーはパプリカ、値は赤
キーは小松菜、値は緑
キーはなすび、値は紫
キーはトマト、値は赤
--*/

forEachとObject.keysの組み合わせでループ処理

基本的にはfor of とObject.keysの組み合わせと一緒です。ただforEachはループ処理を途中で止めることができません。

/*-- 型を指定 --*/
interface Vegetables {
  パプリカ: string;
  小松菜: string;
  なすび: string;
  トマト: string;
  [key: string]: string;
}

/*-- オブジェクト --*/
const vegetable: Vegetables = {
  パプリカ: '赤',
  小松菜: '緑',
  なすび: '紫',
  トマト: '赤',
};

/*-- ループ --*/
Object.keys(vegetable).forEach((key) => {
   console.log(`キーは${key}、値は${vegetable[key]}`);
});

/*-- 以下はconsole.logで表示されたもの 
キーはパプリカ、値は赤
キーは小松菜、値は緑
キーはなすび、値は紫
キーはトマト、値は赤
--*/

mapとObject.keysの組み合わせ

mapを使った場合、インデックス番号も取得できます。ただ、処理スピードは他のループと比べて遅いです。

/*-- 型を指定 --*/
interface Vegetables {
  パプリカ: string;
  小松菜: string;
  なすび: string;
  トマト: string;
  [key: string]: string;
}

/*-- オブジェクト --*/
const vegetable: Vegetables = {
  パプリカ: '赤',
  小松菜: '緑',
  なすび: '紫',
  トマト: '赤',
};

Object.keys(vegetable).map((key, index) => {
  console.log(`番号は${index + 1}で、キーは${key}で、カラーは${vegetable[key]}`);
});

/*-- 以下はconsole.logで表示されたもの
番号は1で、キーはパプリカで、カラーは赤
番号は2で、キーは小松菜で、カラーは緑
番号は3で、キーはなすびで、カラーは紫
番号は4で、キーはトマトで、カラーは赤
--*/

for inでループ処理

一番シンプルに書けるのがfor inのループです。メリットは処理スピードが速い点ですが、注意する点も2つあります。

  • prototype拡張で追加したもループに入ってくる
  • オブジェクトが並んだ順番通りに処理されることが保証されていない
/*-- 型を指定 --*/
interface Vegetables {
  パプリカ: string;
  小松菜: string;
  なすび: string;
  トマト: string;
  [key: string]: string;
}

/*-- オブジェクト --*/
const vegetable: Vegetables = {
  パプリカ: '赤',
  小松菜: '緑',
  なすび: '紫',
  トマト: '赤',
};

/*-- ループ --*/
for (const key in vegetable) {
  console.log(`キーは${key}、値は${vegetable[key]}`);
}

/*-- 以下はconsole.logで表示されたもの 
キーはパプリカ、値は赤
キーは小松菜、値は緑
キーはなすび、値は紫
キーはトマト、値は赤
--*/

fo inでオブジェクトをループすると、prototype拡張で追加されたメソッドも入ってきます。それを避けるには以下のような条件分岐が必要になります。vegetableオブジェクトの中に、パプリカや小松菜などキーがあればtrueを返してくれます。

for (const key in vegetable) {
  if (Object.prototype.hasOwnProperty.call(vegetable, key)) {
    console.log(`これはfor inキーは${key}、値は${vegetable[key]}`);
  }
}

個人的にはTypeScriptでprototypeを拡張させるイメージが湧きませんが、プロジェクトや開発環境によっては条件分岐したほうが安全かもしれません。

条件分岐の書き方については、以下のESLintのページを参考にしました。

オブジェクトに後からプロパティを追加する場合は?

オブジェクトに、後からプロパティを追加していく場合については以下です。
プロパティのキーがstring型であって、値もstring型だとします。

//値がstring型の場合
interface Vegetables {
  [key: string]: string;
}

const vegetables: Vegetables = {};

vegetables.トマト = '赤色'; //プロパティを追加
vegetables.とうもろこし = "黄色"; //プロパティを追加

console.log(vegetables); //{トマト: '赤色', とうもろこし: '黄色'}

/*-- ループ --*/
Object.keys(vegetables).forEach((key) => {
   console.log(`キーは${key}、値は${vegetables[key]}`);
});

/*-- 以下はconsole.logで表示されたもの 
キーはトマト、値は赤色
キーはとうもろこし、値は黄色
--*/

これでinterfaceに固定のキーを指定しなくても、キーにstring型があって、値にstring型のプロパティであればオブジェクトにいくつでも入れることできます。

オブジェクトの階層が深い場合は?

例えば以下のようにオブジェクトのキーに、さらにオブジェクトが配置されている場合はについて説明します。

const vegetableKinds = {
  パプリカ: { 値段: 100, 色: '黄' },
  小松菜: { 値段: 200, 色: '緑' },
  なすび: { 値段: 300, 色: '紫' },
  トマト: { 値段: 400, 色: '赤' },
};

キーの型は、interfaceのVegetableKindsで指定して、値のオブジェクトの型については、interfaceのVegetableDetailsで指定します。

interface VegetableDetails {
  値段: number;
  色: string;
}
interface VegetableKinds { 
  [key: string]: VegetableDetails;
}
const vegetableKinds: VegetableKinds = {
  パプリカ: { 値段: 100, 色: '黄' },
  小松菜: { 値段: 200, 色: '緑' },
  なすび: { 値段: 300, 色: '紫' },
  トマト: { 値段: 400, 色: '赤' },
};

for (const key of Object.keys(vegetableKinds)) {
  console.log(`キーは${key}、値段は${vegetableKinds[key].値段}、色は${vegetableKinds[key].色}`);
}

/*-- 以下はconsole.logで表示されたもの 
キーはパプリカ、値段は100、色は黄
キーは小松菜、値段は200、色は緑
キーはなすび、値段は300、色は紫
キーはトマト、値段は400、色は赤
--*/

オブジェクトのループはどれが良いのか

4つあるオブジェクトのループでは、for of とObject.keysの組み合わせが処理スピードも早く、途中でループと止めることもできるので一番柔軟に使えます。

どのループも設計が正しく整理できていれば使えます。処理内容に合わせて使い分けてみてください。

ループの特徴をまとめておきます。

  • for ofとObject.keysの組み合わせ … 処理スピードの速さ、ループを止められるなど安定
  • forEachとObject.keysの組み合わせ … ループを途中で止められない
  • mapとObject.keysの組み合わせ … インデックス番号が取れる。処理が遅い
  • for in … 処理スピードは最も早い。処理の順番が保証されていない