Astroでつくったネスト構造のulリストのスニペット

趣味のサイトをSSGのAstroでつくっているときに、ネスト構造のulリストをつくったので、そのスニペットコードです。

Astroは、
> JSXに似た式
[Astroコンポーネント 🚀 Astroドキュメント | JSXに似た式](https://docs.astro.build/ja/core-concepts/astro-components/#jsx%E3%81%AB%E4%BC%BC%E3%81%9F%E5%BC%8F)

なので、この程度のことであれば、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;`を当てておくことで、[目標のかたち](#目標のかたち)のようにインデントされます。
```HTML

<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>
```

# やったこと

```jsx:index.astro
---
import List from '~/components/List.astro';
import { list } from '~/terms';
---
<List list={list} deep="0"></List>
```

```jsx:List.astro
---
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>
```
```jsx:ListElement.astro
---
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ドキュメント | 動的なタグ](https://docs.astro.build/ja/core-concepts/astro-components/#%E5%8B%95%E7%9A%84%E3%81%AA%E3%82%BF%E3%82%B0)


## 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}
```
の形にしてしまえば、解決な気がします。試してないけど。

# 考え方
このコードにたどりつくための作業は以下の様な感じです。

0. ネスト構造の繰り返しを意識して、元データを作成する
0. 元データを元に、手に入れたいHTMLコードを手動で書いてみる
0. 以上の工程でやりたいことのイメージを固める
0. 再帰処理を書くため、基本となる構造をHTMLコードから切り出す
0. 切り出した構造を出力するJSコードを書く
0. 再帰処理の行き止まり処理を考える（今回の場合は`children`がなくなること）

こんな感じだったと思うけど、二三度考え直して、コードを仕切り直していたりするので、右往左往しながら、この回答にたどりつきました。

# 後書き
次に再帰処理を書くときは、もっとスラスラと書けるようになりたいな。