Reactの配列メソッドと向き合う
JavaScript(React)での配列操作は、データの取得から整形・表示まで、map、filter、reduceといったメソッドを使うことで、処理の意図が明確で保守しやすいコードを書きやすくなる。
たとえば、以下のようなJSON形式の三重県の市町村データがあるとする。
// 令和6年10月1日の三重県の市区町村
const mieCities = [
{ "name": "四日市市", "area": "北勢", "population": 300457 },
{ "name": "桑名市", "area": "北勢", "population": 135308 },
{ "name": "鈴鹿市", "area": "北勢", "population": 191153 },
{ "name": "いなべ市", "area": "北勢", "population": 44080 },
{ "name": "東員町", "area": "北勢", "population": 25638 },
{ "name": "菰野町", "area": "北勢", "population": 39757 },
{ "name": "朝日町", "area": "北勢", "population": 11105 },
{ "name": "川越町", "area": "北勢", "population": 15594 },
{ "name": "津市", "area": "中勢", "population": 267001 },
{ "name": "尾鷲市", "area": "東紀州", "population": 14528 }
// 以下、略
];
このデータをReactコンポーネントで扱うとき、たとえばmapで市町村名の一覧を作り、filterで特定の地域を抽出し、reduceで人口の合計を求めるといった処理が書ける。
import React, { useEffect, useState } from 'react';
const MieMunicipalities = () => {
const [cities, setCities] = useState;
useEffect(() => {
const fetchData = async () => {
const res = await fetch('/data/mie.json'); // 三重県の市町村データを取得
const data = await res.json();
setCities(data);
};
fetchData();
}, []);
// map:全市町村名
const allNames = cities.map(city => city.name);
// filter:北勢エリア
const hokuseiCities = cities.filter(city => city.area === '北勢');
// reduce:北勢の人口合計
const totalHokuseiPopulation = hokuseiCities.reduce(
(sum, city) => sum + city.population,
0
);
return (
<div>
<h2>三重県の市町村(全て)</h2>
<ul>
{allNames.map(name => (
<li key={name}>{name}</li>
))}
</ul>
<h2>北勢エリアの市町村</h2>
<ul>
{hokuseiCities.map(city => (
<li key={city.name}>
{city.name}(人口:{city.population.toLocaleString()}人)
</li>
))}
</ul>
<h2>北勢エリアの総人口:{totalHokuseiPopulation.toLocaleString()}人</h2>
</div>
);
};
export default MieMunicipalities;
mapはデータから特定のプロパティを抜き出したり、表示に適した形に変換したりする処理でよく使われる。filterは特定条件に合う要素のみを抽出し、reduceは合計・平均などの集約処理に向いている。
これらはいずれも元の配列を変更せず、新しい値を返す「非破壊的メソッド」として扱われる。配列の破壊的操作については、サバイバルTypeScriptのこちらのドキュメントを参照。
同じ処理はfor文やif文でも書くことができる。たとえば、次のように実装する。
import React, { useEffect, useState } from 'react';
const MieMunicipalities = () => {
const [cities, setCities] = useState;
useEffect(() => {
const fetchData = async () => {
const res = await fetch('/data/mie.json');
const data = await res.json();
setCities(data);
};
fetchData();
}, []);
// 全市町村名
const allCityNameList = (() => {
const list = [];
for (let i = 0; i < cities.length; i++) {
list.push(<li key={cities[i].name}>{cities[i].name}</li>);
}
return list;
})();
// 北勢エリア
const hokuseiCityList = (() => {
const list = [];
for (let i = 0; i < cities.length; i++) {
if (cities[i].area === '北勢') {
list.push(
<li key={cities[i].name}>
{cities[i].name}(人口:{cities[i].population.toLocaleString()}人)
</li>
);
}
}
return list;
})();
// 北勢の人口合計
const totalHokuseiPopulation = (() => {
let total = 0;
for (let i = 0; i < cities.length; i++) {
if (cities[i].area === '北勢') {
total += cities[i].population;
}
}
return total;
})();
return (
<div>
<h2>三重県の市町村(全て)</h2>
<ul>{allCityNameList}</ul>
<h2>北勢エリアの市町村</h2>
<ul>{hokuseiCityList}</ul>
<h2>北勢エリアの総人口:{totalHokuseiPopulation.toLocaleString()}人</h2>
</div>
);
};
export default MieMunicipalities;
このように、for文やif文といった記述(いわゆる手続き型プログラミング)でも同じ結果は得られるが、そこでは「何をしたいのか(what)」ではなく「どう処理するか(how)」の記述が中心になる。処理の手順に意識が引っ張られ、意図が見えにくくなるため、ロジックの読み取りや保守が難しくなりやすい。特に、処理が複雑だったり、ループ内に複数の目的が混在していたりする場合はなおさらだ。
一方で、map、filter、reduceといったメソッドは、配列に対して「どう処理するか」を内部に隠しつつ、「何を得たいか」を一文で表現できる。これが、いわゆる宣言的プログラミングの特徴だ。
Reactは宣言的にUIを記述できることが特徴で、mapやfilter、reduceといった配列メソッドはその考え方と自然にマッチする。
「JavaScriptのメソッドは多すぎて忘れてしまうよー」と感じることが自分はしばしばあるのだが、こうしたメソッドの背景には、配列操作をより宣言的に、より意図が明快な形で表現したいという言語進化の文脈があると考えれば、各処理に対して専用のメソッドが用意されている理由も納得できる。
Discussion
最近だと 配列の values() から map() filter() drop() toArray() 等 が使えるので配列の件数をあまり考えなくてよくなったのもよいですね。