メインコンテンツまでスキップ

繰り返し処理

繰り返し処理を記述する際のベストプラクティスを学びましょう。

学習のポイント

  • for文よりも高階関数を使う
  • forEach, map, filter, reduceの使い分け
  • for...of と for...in の違いを理解する
  • 適切なループ構文を選択する

for文よりも高階関数を使う

JavaScriptには配列を操作するための高階関数(forEach, map, filter, reduce など)が用意されています。これらを使うことで、よりシンプルで意図が明確なコードを書くことができます。

Bad

❌ Bad: for文でのループ
// ❌ for文でのループ
const numbers: number[] = [1, 2, 3, 4, 5];
const doubled: number[] = [];

for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}

Good

✅ Good: mapを使用
// ✅ mapを使用
const numbers: number[] = [1, 2, 3, 4, 5];
const doubled: number[] = numbers.map(num => num * 2);

forEach の使い方

forEach は配列の各要素に対して処理を実行する高階関数です。戻り値はありません。

❌ Bad: for文
// forEachの基本
const fruits: string[] = ['apple', 'banana', 'orange'];

// ❌ for文
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}

// ✅ forEach
fruits.forEach(fruit => {
console.log(fruit);
});

// インデックスが必要な場合
fruits.forEach((fruit, index) => {
console.log(`${index + 1}. ${fruit}`);
});

注意点

forEach は途中で処理を中断できません。中断が必要な場合は for...ofsome/every を使いましょう。

❌ Bad: forEachは途中で抜けられない(breakが使えない)
// ❌ forEachは途中で抜けられない(breakが使えない)
const numbers: number[] = [1, 2, 3, 4, 5];
numbers.forEach(num => {
if (num === 3) return; // これは次の要素に進むだけ
console.log(num);
});
// 出力: 1, 2, 4, 5

// ✅ 途中で抜けたい場合は for...of を使う
for (const num of numbers) {
if (num === 3) break;
console.log(num);
}
// 出力: 1, 2

// ✅ または some/every を使う
numbers.some(num => {
if (num === 3) return true; // trueを返すと終了
console.log(num);
return false;
});
// 出力: 1, 2

map の使い方

map は配列の各要素を変換して新しい配列を生成します。

interface User {
id: number;
name: string;
email: string;
}

const users: User[] = [
{ id: 1, name: 'Taro', email: 'taro@example.com' },
{ id: 2, name: 'Hanako', email: 'hanako@example.com' },
];

// ❌ for文
const names: string[] = [];
for (let i = 0; i < users.length; i++) {
names.push(users[i].name);
}

// ✅ map
const userNames: string[] = users.map(user => user.name);
// ['Taro', 'Hanako']

// オブジェクトの変換
interface UserSummary {
id: number;
displayName: string;
}

const summaries: UserSummary[] = users.map(user => ({
id: user.id,
displayName: `${user.name} (${user.email})`,
}));

filter の使い方

filter は条件に一致する要素だけを抽出して新しい配列を生成します。

interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}

const products: Product[] = [
{ id: 1, name: 'Apple', price: 100, inStock: true },
{ id: 2, name: 'Banana', price: 80, inStock: false },
{ id: 3, name: 'Orange', price: 120, inStock: true },
];

// ❌ for文
const inStockProducts: Product[] = [];
for (let i = 0; i < products.length; i++) {
if (products[i].inStock) {
inStockProducts.push(products[i]);
}
}

// ✅ filter
const availableProducts: Product[] = products.filter(product => product.inStock);

// 複数の条件
const affordableAndAvailable: Product[] = products.filter(
product => product.inStock && product.price <= 100
);

// 型ガードと組み合わせる
const values: (string | null)[] = ['a', null, 'b', null, 'c'];
const strings: string[] = values.filter((v): v is string => v !== null);

reduce の使い方

reduce は配列を単一の値に集約します。

const numbers: number[] = [1, 2, 3, 4, 5];

// 合計を計算
const sum: number = numbers.reduce((acc, num) => acc + num, 0);
// 15

// オブジェクトに集約
interface Item {
category: string;
name: string;
}

const items: Item[] = [
{ category: 'fruit', name: 'Apple' },
{ category: 'vegetable', name: 'Carrot' },
{ category: 'fruit', name: 'Banana' },
];

const grouped = items.reduce<Record<string, string[]>>((acc, item) => {
if (!acc[item.category]) {
acc[item.category] = [];
}
acc[item.category].push(item.name);
return acc;
}, {});
// { fruit: ['Apple', 'Banana'], vegetable: ['Carrot'] }

メソッドチェーンを活用する

高階関数はチェーンして使うことで、複雑な処理を段階的に記述できます。

interface Transaction {
id: number;
type: 'income' | 'expense';
amount: number;
date: Date;
}

const transactions: Transaction[] = [
{ id: 1, type: 'income', amount: 1000, date: new Date('2024-01-01') },
{ id: 2, type: 'expense', amount: 500, date: new Date('2024-01-02') },
{ id: 3, type: 'income', amount: 2000, date: new Date('2024-01-03') },
{ id: 4, type: 'expense', amount: 300, date: new Date('2024-01-04') },
];

// 収入の合計を計算
const totalIncome = transactions
.filter(t => t.type === 'income')
.map(t => t.amount)
.reduce((sum, amount) => sum + amount, 0);
// 3000

// 2024年1月の支出一覧を取得
const januaryExpenses = transactions
.filter(t => t.type === 'expense')
.filter(t => t.date.getMonth() === 0 && t.date.getFullYear() === 2024)
.map(t => ({ id: t.id, amount: t.amount }));

for...of と for...in の違い

for...of

配列のを反復処理します。

const fruits: string[] = ['apple', 'banana', 'orange'];

for (const fruit of fruits) {
console.log(fruit);
}
// apple, banana, orange

// Map, Setにも使える
const map = new Map<string, number>([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}

for...in

オブジェクトのキーを反復処理します。

const person = { name: 'Taro', age: 25, city: 'Tokyo' };

for (const key in person) {
console.log(`${key}: ${person[key as keyof typeof person]}`);
}
// name: Taro, age: 25, city: Tokyo

// ⚠️ 配列には使わない方が良い
const arr = ['a', 'b', 'c'];
for (const index in arr) {
console.log(index); // '0', '1', '2' (文字列)
}

使い分け

✅ Good: 配列の値を処理 → for...of
// ✅ 配列の値を処理 → for...of
const numbers: number[] = [1, 2, 3];
for (const num of numbers) {
console.log(num);
}

// ✅ オブジェクトのキーを処理 → for...in または Object.keys/entries
const obj = { a: 1, b: 2 };

// for...in
for (const key in obj) {
console.log(key, obj[key as keyof typeof obj]);
}

// Object.entries(推奨)
for (const [key, value] of Object.entries(obj)) {
console.log(key, value);
}

練習問題

以下の for 文を高階関数に書き直してください
// 問題1: forEach に書き直す
const cities: string[] = ['Tokyo', 'Osaka', 'Kyoto'];
for (let i = 0; i < cities.length; i++) {
console.log(cities[i]);
}

// 問題2: map に書き直す
const prices: number[] = [100, 200, 300];
const taxIncluded: number[] = [];
for (let i = 0; i < prices.length; i++) {
taxIncluded.push(prices[i] * 1.1);
}

// 問題3: filter に書き直す
const scores: number[] = [85, 42, 93, 67, 38, 79];
const passing: number[] = [];
for (let i = 0; i < scores.length; i++) {
if (scores[i] >= 60) {
passing.push(scores[i]);
}
}

解答:

// 問題1: forEach
const cities: string[] = ['Tokyo', 'Osaka', 'Kyoto'];
cities.forEach(city => console.log(city));

// 問題2: map
const prices: number[] = [100, 200, 300];
const taxIncluded: number[] = prices.map(price => price * 1.1);

// 問題3: filter
const scores: number[] = [85, 42, 93, 67, 38, 79];
const passing: number[] = scores.filter(score => score >= 60);

繰り返し処理のまとめ

繰り返し処理のベストプラクティス
  • 単純な反復 → forEach
  • 変換 → map
  • 抽出 → filter
  • 集約 → reduce
  • 配列の値 → for...of
  • オブジェクトのキー → Object.entries
  • 途中で抜ける → for...of + break
  • メソッドチェーンで複雑な処理を表現