Reactで配列内の配列を取り出す
React勉強中のサラリーマンです。
今日はReactのキモであるStateがどうのこうの…の以前に、ネストされた配列の処理について理解に苦労したので、整理するために書いていきます。
例として使う配列
export const recipes = [{
id: 'greek-salad',
name: 'Greek Salad',
ingredients: ['tomatoes', 'cucumber', 'onion', 'olives', 'feta']
}, {
id: 'hawaiian-pizza',
name: 'Hawaiian Pizza',
ingredients: ['pizza crust', 'pizza sauce', 'mozzarella', 'ham', 'pineapple']
}, {
id: 'hummus',
name: 'Hummus',
ingredients: ['chickpeas', 'olive oil', 'garlic cloves', 'lemon', 'tahini']
}];
この配列は、簡単に言えば「料理の配列があって、それぞれの料理には材料の配列が含まれる」というものです。
やりたいこと
上記のコードのままでは、「リスト」として書き出すことができません。
読みにくいので、最終的には以下のようにリストにしたいです。
考え方
大まかな考え方は以下。
トップレベルの配列(全料理)から、「それぞれの料理」を取り出す
各料理に含まれるボトムレベルの配列から、「それぞれの材料」を取り出す
材料がリストとして並ぶ
1. トップレベルの配列から、それぞれの料理を取り出す
export default function RecipeList() {
return (
<div>
<h1>Recipes</h1>
{recipes.map((recipe) => (
<div key={recipe.id}>
<h2>{recipe.name}</h2>
</div>
))}
</div>
);
}
このコードがやっていることは以下。
export default function RecipeList()
RecipeListというコンポーネントを宣言する<h1>Recipes</h1>
Recipesというトップレベルの見出しを描画する{recipes.map((recipe) => (
recipes(全料理)からrecipe(ひとつの料理)を取り出して並べる(map)<div key={recipe.id}> <h2>{recipe.name}</h2>
recipeのid番号を付与した要素として、各recipeの名前を表示する
2-1. それぞれの材料を取り出す
「料理リストの中の材料リスト」のように入れ子構造になっていることをプログラミング用語で「ネストされている」といいます。
このようにネストされた配列を操作するにはいくつかのアプローチがあります。
例えば、呼び出すコード自体をネストしてしまうことです。
{recipes.map(recipe =>
<div key={recipe.id}>
<h2>{recipe.name}</h2>
<ul>
{recipe.ingredients.map(ingredient =>
<li key={ingredient}>
{ingredient}
</li>
)}
</ul>
この書き方を自然言語混じりで言うと、
「料理"recipe"の紹介をします。料理の名前は”recipe.name”で、その材料は<li>これとこれとこれと….</li>です。
という感じになります。
このコードはコンパクトですが、例えば「麻婆豆腐に使う香辛料のリストだけ出して?」などの複雑なリクエストに対応しにくい欠点もあります。
また、コンポーネントの単一責任の原則に照らすと、このコードの役割が「料理リストを作る」「材料リストを作る」の2つの仕事をしているとも読み取れるので、一つのコードに対して役割を乗せすぎであると言えるかも知れません。(このあたりは開発チームの方針にもよるみたいです)
ということで、今回は「材料だけを取り出す」コンポーネントを切り出しました。
const IngredientsList = ({ ingredients }) => (
<ul>
{ingredients.map((ingredient, index) => (
<li key={index}>{ingredient}</li>
))}
</ul>
);
このコードを順に解説すると以下です。
const IngredientsList = ({ ingredients }) => (
ここでは、IngredientsListというコンポーネントを宣言し、ingredientsをpropsとして(引数として?)取ることを示します。<ul> </ul>
記載したい内容がリストなので、リストの始まりと終わりを示すHTMLタグをJSXで記述しています。(HTMLっぽいコードをJavaScriptで書けるのがJSXです){ingredients.map((ingredient, index) => (
ここでは、引数として与えられたingredientsが配列であると想定し、その各要素のingredient(単数系)を取り出しながらindex(連番)を付与していきます。
ここでindexを付与すると、例えば [1. トマト, 2.きゅうり…] のように順番に番号が付与されるため、若い順に並び替えるなどの操作が困難になります。しかし現実的にはトマトときゅうりを並び替えたくなるシーンは無さそうなので、このままいきます。<li key={index}>{ingredient}</li>
作成したindex(連番)を付与し、それぞれの材料”ingredient”をリスト形式で表示します。
このコード(IngredientsListコンポーネント)は、ひとことで言ってしまえば「材料を全部放り込めば、綺麗にならべてくれるマシーン」とでも言いましょうか。
それでは次のセクションでは、「材料を全部放り込む」方法を見ていきます。
2-2. 材料の配列をリスト化する
先ほど、以下のように解説しました。
const IngredientsList = ({ ingredients }) => (
ここでは、IngredientsListというコンポーネントを宣言し、ingredientsをpropsとして(引数として?)取ることを示します。
では、今から材料投入口 { ingredients } に材料を全部放り込みます。
いじるのは、最初のコード
export default function RecipeList() {
return (
<div>
<h1>Recipes</h1>
{recipes.map((recipe) => (
<div key={recipe.id}>
<h2>{recipe.name}</h2>
</div>
))}
</div>
);
}
このコードに処理を追加します。
const RecipeList = () => (
<div>
{recipes.map((recipe) => (
<div key={recipe.id}>
<h2>{recipe.name}</h2>
<IngredientsList ingredients={recipe.ingredients} />
</div>
))}
</div>
);
追加したのは、<IngredientsList ingredients={recipe.ingredients} /> の部分です。
これも切り分けて解説します。
<IngredientsList
ここで、先ほど定義したIngredientsList コンポーネントを呼び出しています。ingredients={recipe.ingredients} />
ここで、propsとしてingredientsを定義し、その料理の材料(recipe.ingredients)を代入しています。
言い換えれば、ingredients(複数形)・・・材料を雑多に放り込める箱
{recipe.ingredients} ・・・その料理のレシピに書かれた材料ぜんぶ
となります。箱に材料をどかっと入れたイメージですね。
箱詰めされた材料たちは、先ほど定義した「材料を全部放り込めば、綺麗にならべてくれるマシーン」に送られます。
3. 入れた材料が並ぶようにする
では、ここまでの順番を簡単に振り返ります。
const RecipeList = () => (
<div>
{recipes.map((recipe) => (
<div key={recipe.id}>
<h2>{recipe.name}</h2>
<IngredientsList ingredients={recipe.ingredients} />
</div>
))}
</div>
);
最後に書いたこのコードは、簡単に言うと以下のことをしています。
全部の料理をリスト化します
リスト化したそれぞれの料理について、IngredientsListコンポーネントに「材料ぜんぶ」の配列を渡して綺麗に並べてもらうよう依頼しています
で、材料をごっそり受け取ったIngredientsListコンポーネントの挙動は以下。
const IngredientsList = ({ ingredients }) => (
<ul>
{ingredients.map((ingredient, index) => (
<li key={index}>{ingredient}</li>
))}
</ul>
);
届いた配列から材料を1個ずつ取り出す。これを全部に対して行う(map)
1個ずつの材料に番号を振っておく(index)
それらの要素をリストに並べる(<ul>ならびに<li>タグ)
出来上がったものを、依頼主に返す
※この部分は、1行目の => という表記によって実現しています。「IngredientsListコンポーネントは次の結果を返しますよ。」という宣言になっています。
まとめ
以上をまとめると、以下のようなことをやりました。
【機能1】たくさんの料理からそれぞれの料理を1個ずつ取り出す
【機能2】それぞれの料理から、それぞれの材料を取り出す機能部品を作る
【機能3】機能1に機能2を入れ込む(ネスト)して、それぞれの料理のそれぞれの材料を取り出すようにする
配列の中の配列を処理するときは処理するコード自体をネストしても良いのですが、可読性が落ちたりコードが柔軟でなくなるおそれもあるので、配列内の配列を処理する専用のコンポーネントを作る考え方の方が、DRYかつ単一責任原則に則っていて良さげです。
今回の題材である教材はReactの公式チュートリアルです。
https://ja.react.dev/learn/rendering-lists
なお公式の模範解答の1つによると、配列の要素を取り出すコードをネストさせてもOKということのようです。
Reactの公式のガイドは、「結局どのようにレンダーされるかが大事で、途中経過は何でもいいよ。ただしこうやると遅くなるから避けたほうが良いかも。」といった、ゆるい感じで教えてくれるので好きです。
以上です。最後までお読みいただきありがとうございました。
この記事が気に入ったらサポートをしてみませんか?