繰り返し処理
繰り返し処理を記述する際のベストプラクティスを学びましょう 。
学習のポイント
- 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...of や some/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 - メソッドチェーンで複雑な処理を表現