趣味のサイトをSSGのAstroでつくっているときに、ネスト構造のulリストをつくったので、そのスニペットコードです。
Astroは、
なので、この程度のことであれば、JSXを使うらしいReactでも通じる内容かもしれませんが、俺はReactをやったことがないので、通じるかどうかはまったくわかりません。
やりたいこと
ネスト構造のリスト(元データ)をulリスト化(目標のかたち)する。
元データ
list: [
{
name: 'Level1-1',
slugs: [1, 2, 3],
children: [
{
name: 'Level2-1',
slugs: [4, 5],
children: [
{
name: 'Level3-1',
slugs: [6, 7],
},
{
name: 'Level3-2',
slugs: [8, 9],
},
],
},
{
name: 'Level2-2',
slugs: [10, 11],
},
],
},
{
name: 'Level1-2',
slugs: [12, 13],
},
],
目標のかたち
Level1-1
1
2
3
Level2-1
4
5
Level3-1
6
7
Level3-2
8
9
Level2-2
10
11
Level1-2
12
13
HTMLコードで言うと以下の通りで、スタイルはul
にmargin-left: 40px;
を当てておくことで、目標のかたちのようにインデントされます。
<ul>
<li>
<h1>Level1-1</h1>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>
<h2>Level2-1</h2>
<ul>
<li>4</li>
<li>5</li>
<li>
<h3>Level3-1</h3>
<ul>
<li>6</li>
<li>7</li>
</ul>
<h3>Level3-2</h3>
<ul>
<li>8</li>
<li>9</li>
</ul>
</li>
</ul>
<h2>Level2-1</h2>
<ul>
<li>10</li>
<li>11</li>
</ul>
</li>
</ul>
</li>
<li>
<h1>Level1-2</h1>
<ul>
<li>12</li>
<li>13</li>
</ul>
</li>
</ul>
やったこと
---
import List from '~/components/List.astro';
import { list } from '~/terms';
---
<List list={list} deep="0"></List>
---
import ListElement from '~/components/ListElement.astro';
const {list,deep} = Astro.props;
const nextDeep = Number(deep) + 1;
---
<ul>
{
list.map(el => (
<li><ListElement list={el} deep={nextDeep}></ListElement></li>
))
}
</ul>
---
import ListElement from '~/components/ListElement.astro';
const { list, deep } = Astro.props;
const Heading = `h${deep}`;
const nextDeep = Number(deep) + 1;
---
<Heading>{list.name}</Heading>
<ul>
{list.slugs.map((slug) => <li>{slug}</li>)}
{
list.children?.map((el) => (
<li><ListElement list={el} deep={nextDeep}></ListElement></li>
))
}
</ul>
ひっかかった場所
H1、H2タグ
<h${deep}>
みたいなことをしようとして、当然うまくいかなかった。
以下を参照して解決しました。
Astroコンポーネント 🚀 Astroドキュメント | 動的なタグ
deep変数
最初、以下の様に書いて、<h11>
となって、なんだこりゃとなりました。
const nextDeep = deep + 1;
そうですね、String型だからですね。
ひっかかっていること
List.astro
とListElement.astro
に同じコードが出現しているので、ここをどうにかできるはずです。
同じコードをコンポーネント化すると、残ったコードが簡素になるので、
<Heading>{list.name}</Heading>
{list.slugs.map((slug) => <li>{slug}</li>)}
この出力箇所を、データの有無で出し分けすれば、1つのファイルで事足りるかなあと想像しています。
追記(2023/03/12 16:06)
この投稿を書きながら考えていたら、元データの最上層も
{ name, slugs, children}
の形にしてしまえば、解決な気がします。試してないけど。
考え方
このコードにたどりつくための作業は以下の様な感じです。
- ネスト構造の繰り返しを意識して、元データを作成する
- 元データを元に、手に入れたいHTMLコードを手動で書いてみる
- 以上の工程でやりたいことのイメージを固める
- 再帰処理を書くため、基本となる構造をHTMLコードから切り出す
- 切り出した構造を出力するJSコードを書く
- 再帰処理の行き止まり処理を考える(今回の場合は
children
がなくなること)
こんな感じだったと思うけど、二三度考え直して、コードを仕切り直していたりするので、右往左往しながら、この回答にたどりつきました。
後書き
次に再帰処理を書くときは、もっとスラスラと書けるようになりたいな。