JavaScript reduce を使った業務実装パターン|実務で役立つ配列処理テクニック
JavaScriptのreduceメソッドは、配列を処理して単一の値を生成する強力なツールです。しかし、実務ではどのように活用すればよいのか、具体的なシーンが思いつかない開発者も多いのではないでしょうか。本記事では、実務でよく出現するreduceのユースケースを、実装コードとともに解説します。
reduceメソッドの簡易解説
reduceメソッドは、配列の各要素に対して関数を実行し、その結果を次のイテレーションへ受け渡すことで、最終的に1つの値を生成します。
const result = array.reduce((accumulator, currentValue, index, array) => {
// accumulatorに値を蓄積していく
return accumulator;
}, initialValue);
各パラメータの意味:
- accumulator:前回のコールバック実行の戻り値、または初期値
- currentValue:処理中の現在の配列要素
- index:現在処理中の要素のインデックス
- initialValue:accumulatorの初期値(省略可)
業務でのユースケース
ユースケース1:数値の合計・集計
最も基本的で頻出のパターンです。ECサイトの売上集計やユーザーのスコア計算などで使用します。
// 商品の販売金額を集計する例
const sales = [
{ id: 1, amount: 5000, category: 'electronics' },
{ id: 2, amount: 3000, category: 'books' },
{ id: 3, amount: 7500, category: 'electronics' },
{ id: 4, amount: 2000, category: 'books' }
];
// 全体の売上を計算
const totalSales = sales.reduce((sum, item) => sum + item.amount, 0);
console.log(totalSales); // 17500
// カテゴリー別の売上を集計
const salesByCategory = sales.reduce((acc, item) => {
acc[item.category] = (acc[item.category] || 0) + item.amount;
return acc;
}, {});
console.log(salesByCategory);
// { electronics: 12500, books: 5000 }
ユースケース2:配列をオブジェクトに変換
APIレスポンスのデータを、IDをキーとしたオブジェクトに変換する際に活躍します。検索や更新処理が効率化されます。
// ユーザー一覧をIDをキーにしたオブジェクトへ変換
const users = [
{ id: 101, name: '田中太郎', department: '営業' },
{ id: 102, name: '鈴木花子', department: 'エンジニア' },
{ id: 103, name: '佐藤次郎', department: '営業' }
];
const usersById = users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {});
console.log(usersById[102]);
// { id: 102, name: '鈴木花子', department: 'エンジニア' }
// さらに、特定フィールドのみを抽出する場合
const userNames = users.reduce((acc, user) => {
acc[user.id] = user.name;
return acc;
}, {});
console.log(userNames);
// { 101: '田中太郎', 102: '鈴木花子', 103: '佐藤次郎' }
ユースケース3:配列のフィルタリングと変換を同時に行う
データベースクエリのフィルタリングとマッピングを一度に処理することで、コード量を削減し、パフォーマンスも向上させます。
// 注文データから、特定条件を満たす項目を抽出・変換
const orders = [
{ id: 1, status: 'completed', total: 10000, customerId: 5 },
{ id: 2, status: 'pending', total: 5000, customerId: 10 },
{ id: 3, status: 'completed', total: 15000, customerId: 5 },
{ id: 4, status: 'cancelled', total: 3000, customerId: 15 },
{ id: 5, status: 'completed', total: 8000, customerId: 10 }
];
// ステータスが'completed'の注文を抽出し、顧客IDと金額のみにする
const completedOrderSummary = orders.reduce((acc, order) => {
if (order.status === 'completed') {
acc.push({
customerId: order.customerId,
amount: order.total
});
}
return acc;
}, []);
console.log(completedOrderSummary);
// [
// { customerId: 5, amount: 10000 },
// { customerId: 5, amount: 15000 },
// { customerId: 10, amount: 8000 }
// ]
ユースケース4:グループ化処理
データを複数のグループに分類する際に、reduceは非常に強力です。レポーティング機能やダッシュボード作成で頻用されます。
// 従業員データを部門別にグループ化
const employees = [
{ name: '田中太郎', department: '営業', salary: 350000 },
{ name: '鈴木花子', department: 'エンジニア', salary: 450000 },
{ name: '佐藤次郎', department: '営業', salary: 380000 },
{ name: '伊藤美咲', department: 'エンジニア', salary: 480000 },
{ name: '加藤健太', department: '企画', salary: 400000 }
];
const groupedByDept = employees.reduce((acc, emp) => {
const dept = emp.department;
if (!acc[dept]) {
acc[dept] = [];
}
acc[dept].push(emp);
return acc;
}, {});
console.log(groupedByDept);
// {
// 営業: [
// { name: '田中太郎', department: '営業', salary: 350000 },
// { name: '佐藤次郎', department: '営業', salary: 380000 }
// ],
// エンジニア: [
// { name: '鈴木花子', department: 'エンジニア', salary: 450000 },
// { name: '伊藤美咲', department: 'エンジニア', salary: 480000 }
// ],
// 企画: [
// { name: '加藤健太', department: '企画', salary: 400000 }
// ]
// }
実装コード:実務での活用例
ここでは、eコマースプラットフォームでよく出現するシーンを想定したコードを紹介します。
例:注文集計レポートの生成
// 日次の注文データから複数の集計値を一度に算出
const dailyOrders = [
{ orderId: 1, amount: 12000, status: 'completed', paymentMethod: 'card' },
{ orderId: 2, amount: 5500, status: 'completed', paymentMethod: 'bank' },
{ orderId: 3, amount: 0, status: 'cancelled', paymentMethod: 'card' },
{ orderId: 4, amount: 8300, status: 'completed', paymentMethod: 'card' },
{ orderId: 5, amount: 3200, status: 'pending', paymentMethod: 'bank' }
];
const report = dailyOrders.reduce((acc, order) => {
// 全体の統計
acc.totalOrders++;
if (order.status === 'completed') {
acc.completedOrders++;
acc.totalRevenue += order.amount;
// 支払い方法別の集計
if (!acc.paymentMethodBreakdown[order.paymentMethod]) {
acc.paymentMethodBreakdown[order.paymentMethod] = 0;
}
acc.paymentMethodBreakdown[order.paymentMethod]++;
}
if (order.status === 'cancelled') {
acc.cancelledOrders++;
}
if (order.status === 'pending') {
acc.pendingOrders++;
}
return acc;
}, {
totalOrders: 0,
completedOrders: 0,
cancelledOrders: 0,
pendingOrders: 0,
totalRevenue: 0,
paymentMethodBreakdown: {}
});
console.log(report);
// {
// totalOrders: 5,
// completedOrders: 3,
// cancelledOrders: 1,
// pendingOrders: 1,
// totalRevenue: 25800,
// paymentMethodBreakdown: { card: 2, bank: 1 }
// }
例:データの階層変換(TypeScript版)
TypeScriptを使用した実務例です。型安全性を確保しながらreduceを使用できます。
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}
interface ProductCatalog {
available: Product[];
outOfStock: Product[];
totalValue: number;
}
const products: Product[] = [
{ id: 1, name: 'ノートPC', price: 120000, inStock: true },
{ id: 2, name: 'マウス', price: 3000, inStock: false },
{ id: 3, name: 'キーボード', price: 8000, inStock: true },
{ id: 4, name: 'モニター', price: 35000, inStock: false }
];
const catalog: ProductCatalog = products.reduce(
(acc, product) => {
if (product.inStock) {
acc.available.push(product);
} else {
acc.outOfStock.push(product);
}
acc.totalValue += product.price;
return acc;
},
{
available: [],
outOfStock: [],
totalValue: 0
}
);
console.log(catalog);
// {
// available: [
// { id: 1, name: 'ノートPC', price: 120000, inStock: true },
// { id: 3, name: 'キーボード', price: 8000, inStock: true }
// ],
// outOfStock: [
// { id: 2, name: 'マウス', price: 3000, inStock: false },
// { id: 4, name: 'モニター', price: 35000, inStock: false }
// ],
// totalValue: 166000
// }
よくある応用パターン
パターン1:配列の重複排除
reduceを使って配列から重複要素を効率的に除去できます。
const ids = [1, 2, 3, 2, 4, 1, 5, 3];
// オブジェクトをaccumulatorにして重複判定
const uniqueIds = ids.reduce((acc, id) => {
if (!acc.seen[id]) {
acc.seen[id] = true;
acc.result.push(id);
}
return acc;
}, { seen: {}, result: [] }).result;
console.log(uniqueIds); // [1, 2, 3, 4, 5]
// より簡潔な方法
const uniqueIds2 = ids.reduce((acc, id) =>
acc.includes(id) ? acc : [...acc, id], []);
console.log(uniqueIds2); // [1, 2, 3, 4, 5]
パターン2:複数の値を並行して処理
reduceで複数の集計値を一度に計算することで、ループ回数を減らしパフォーマンスを向上させます。
const scores = [85, 92, 78, 95, 88, 73, 91];
const statistics = scores.reduce((acc, score) => {
acc.sum += score;
acc.count++;
acc.max = Math.max(acc.max, score);
acc.min = Math.min(acc.min, score);
return acc;
}, { sum: 0, count: 0, max: 0, min: 100 });
statistics.average = statistics.sum / statistics.count;
console.log(statistics);
// {
// sum: 602,
// count: 7,
// max: 95,
// min: 73,
// average: 86
// }
パターン3:条件付きの複数値変換
// eコマースの注文から、各ステータスごとに合計金額と商品数を算出
const transactions = [
{ status: 'delivered', items: 2, total: 8500 },
{ status: 'pending', items: 1, total: 3000 },
{ status: 'delivered', items: 3, total: 15200 },
{ status: 'cancelled', items: 1, total: 2000 },
{ status: 'pending', items: 2, total: 9800 }
];
const statusAnalysis = transactions.reduce((acc, txn) => {
const status = txn.status;
if (!acc[status]) {
acc[status] = { count: 0, totalItems: 0, totalAmount: 0 };
}
acc[status].count++;
acc[status].totalItems += txn.items;
acc[status].totalAmount += txn.total;
return acc;
}, {});
console.log(statusAnalysis);
// {
// delivered: { count: 2, totalItems: 5, totalAmount: 23700 },
// pending: { count: 2, totalItems: 3, totalAmount: 12800 },
// cancelled: { count: 1, totalItems: 1, totalAmount: 2000 }
// }
パフォーマンス面での注意点
reduceは強力ですが、パフォーマンスに関する注意点があります。
注意点1:配列のスプレッド演算子の使用
accumulatorが配列の場合、スプレッド演算子(...acc)を使うと毎回新しい配列が生成されるため、大規模データでは遅くなります。
// ❌ 遅い方法:毎回配列を複製
const slowResult = items.reduce((acc, item) => {
return item.active ? [...acc, item] : acc;
}, []);
// ✅ 速い方法:既存の配列にpushする
const fastResult = items.reduce((acc, item) => {
if (item.active) {
acc.push(item);
}
return acc;
}, []);
注意点2:ネストが深い場合の可読性
複雑な処理をreduce内に記述すると、可読性が低下します。処理が複雑な場合は関数を分割することをお勧めします。
// ❌ 可読性が低い
const result = data.reduce((acc, item) => {
if (item.type === 'A' && item.value > 100) {
acc[item.category] = (acc[item.category] || 0) + item.value;
}
return acc;
}, {});
// ✅ より読みやすい
const isValidItem = (item) => item.type === 'A' && item.value > 100;
const addToCategory = (acc, item) => {
acc[item.category] = (acc[item.category] || 0) + item.value;
return acc;
};
const result = data
.filter(isValidItem)
.reduce(addToCategory, {});
注意点3:初期値の省略による問題
初期値を省略すると、空配列で予期しないエラーが発生する可能性があります。
// ❌ 危険:空配列でエラー
const sum = [].reduce((a, b) => a + b); // TypeError
// ✅ 安全:初期値を指定
const sum = [].reduce((a, b) => a + b, 0); // 0
Python版での実装参考
Pythonでも同様の処理が必要な場合、functools.reduceを使用できます。ただし、Pythonではリスト内包表記やdictメソッドを使う方が慣例的です。
from functools import reduce
# 売上集計の例
sales = [
{'id': 1, 'amount': 5000, 'category': 'electronics'},
{'id': 2, 'amount': 3000, 'category': 'books'},
{'id': 3, 'amount': 7500, 'category': 'electronics'}
]
# reduceを使った方法
total = reduce(lambda acc, item: acc + item['amount'], sales, 0)
print(total) # 15500
# より Pythonic な方法(推奨)
total_pythonic = sum(item['amount'] for item in sales)
print(total_pythonic) # 15500
# カテゴリー別集計(dictのgetメソッド推奨)
from collections import defaultdict
sales_by_category = defaultdict(int)
for item in sales:
sales_by_category[item['category']] += item['amount']
print(dict(sales_by_category))
# {'electronics': 12500, 'books': 3000}
まとめ
JavaScriptのreduceメソッドは、配列処理における強力なツールです。数値の合計、オブジェクトへの変換、データのグループ化、複数値の並行計算など、業務で頻出するシーンに対応できます。
reduceを使うべき場面:
- 配列から単一の値を生成したい
- 複数の処理を一度のループで完結させたい
- 複雑なデータ構造を組み立てたい
reduceを避けるべき場面:
- 単純なフィルタリングはfilterで十分
- 各要素に処理を施すだけならmapで十分
- 処理が複雑で可読性が低下する場合
実務では、reduceの強力さと可読性のバランスを取ることが重要です。上記のコード例を参考に、プロジェクトの要件に応じて適切に活用してください。

