背景
JavaScriptのreduce
というメソッドは、存在は知っていても実際にどのような場面で使うのかがわかりにくいことがあります。私も以前はその一人でしたが、最近購入履歴のデータを加工してチャートに反映する機会があり、その時にreduce
の便利さを実感しました。具体的には、Rechartsを使ってグラフを描くために、購入履歴データを集計する必要がありました。
jsのreduceの挙動
まず、reduce
の基本的な挙動について説明いたします。reduce
は配列を一つの値にまとめるための関数ですが、初期値(initialValue
)が数字の場合とオブジェクトの場合で少し使い方が変わります。
初期値が数字の場合
例えば、配列内の数字の合計を求める場合は以下のようにします:
const numbers = [1, 2, 3, 4]; const sum = numbers.reduce((acc, current) => acc + current, 0); console.log(sum); // 10
ここで、acc
は累積値で、current
が現在の要素です。initialValue
として0を渡しているので、最初のacc
は0からスタートします。
初期値がオブジェクトの場合
次に、初期値がオブジェクトの場合を見てみましょう。例えば、購入履歴をカテゴリごとに集計したい場合は以下のようになります:
const purchaseHistories = [ { id: 1, amount: 100, category: { name: '外食' } }, { id: 2, amount: 50, category: { name: '交際費' } }, { id: 3, amount: 100, category: { name: 'Netflix' } }, { id: 4, amount: 150, category: { name: '外食' } }, ]; const categoryTotals = purchaseHistories.reduce((acc, history) => { const category = history.category.name; if (!acc[category]) { acc[category] = 0; } acc[category] += history.amount; return acc; }, {}); console.log(categoryTotals); // { '外食': 250, '交際費': 50, 'Netflix': 100 }
ここで、初期値として空のオブジェクト{}
を渡しているため、各カテゴリの合計金額を計算することができます。
実際に利用した場面
次に、実際にどのように使ったかを見てみましょう。
上記のreduce
を使って集計したデータを元に、Rechartsで画像の様なパイチャートを描きます。
以下のコードをご覧ください:
'use client' import { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts' const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042'] const RADIAN = Math.PI / 180 interface CustomizedLabelProps { cx: number cy: number midAngle: number innerRadius: number outerRadius: number percent: number index: number name: string value: number } const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, percent, index, name, value, }: CustomizedLabelProps) => { const radius = innerRadius + (outerRadius - innerRadius) * 0.5 const x = cx + radius * Math.cos(-midAngle * RADIAN) const y = cy + radius * Math.sin(-midAngle * RADIAN) return ( <text x={x} y={y} fill="white" textAnchor={x > cx ? 'start' : 'end'} dominantBaseline="central" fontSize="13px" > {`${name} ${(percent * 100).toFixed(0)}%`} </text> ) } const purchaseHistories = [ {id: 1, amount: 100 ,category: { name: '外食'}}, {id: 2, amount: 50 ,category: { name: '交際費'}}, {id: 3, amount: 100 ,category: { name: 'Netflix'}}, {id: 4, amount: 150 ,category: { name: '外食'}}, ] const RechartPipeChart = () => { const groupByCategoryPurchaseHistories = () => { const categoryTotals = purchaseHistories.reduce( (acc: Record<string, number>, history) => { const category = history.category.name if (!acc[category]) { acc[category] = 0 } acc[category] += history.amount return acc }, {}, ) return Object.keys(categoryTotals).map((category) => ({ name: category, value: categoryTotals[category], })) } const groupedData = groupByCategoryPurchaseHistories() return ( <> <ResponsiveContainer width="100%" height={300}> <PieChart width={500} height={500}> <Pie data={groupedData} cx="50%" cy="50%" labelLine={false} label={({ cx, cy, midAngle, innerRadius, outerRadius, percent, index, }) => renderCustomizedLabel({ cx, cy, midAngle, innerRadius, outerRadius, percent, index, name: groupedData[index].name, value: groupedData[index].value, }) } outerRadius={150} fill="#8884d8" dataKey="value" > {groupedData.map((entry, index) => ( <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} /> ))} </Pie> </PieChart> </ResponsiveContainer> </> ) } export default RechartPipeChart
ここでは、purchaseHistories
をreduce
を使ってカテゴリごとに集計し、その結果をmap
でRechartsが扱いやすい形に変換しています。これにより、購入履歴のデータを元にしたパイチャートを表示することができます。
まとめ
reduce
は一見難しそうに見えますが、使い方を覚えるとデータを集計したり変換したりするのに非常に便利です。特に初期値をうまく設定することで、様々なデータの操作が可能になります。今回の例では、購入履歴のデータをカテゴリごとに集計してチャートに反映する方法をご紹介しましたが、他にも多くの場面で応用できますので、ぜひ試してみてください。