【React】Context を使う前に #1 無駄なコンポーネントの層を削れ

Props のバケツリレー (Props Drilling) を解決するときに、安易に Context を使ったり、状態管理ライブラリ(Recoil, Jotai, Redux)に頼っていませんか？

そんなことをせずとも、「CompA が CompB を使い、CompB が CompC を使い、 CompC が ...」という**依存関係のチェーンを浅くする**のが最善の解決策である場合があります。

:::note
「ローカルとはいえない、真にグローバルな状態[^1] である」なら、Recoil や Jotai が使えますし、 Context には Context の活躍できる場面[^2] があります。今回はそれらの話はしません。
:::

[^1]: https://zenn.dev/yoshiko/articles/607ec0c9b0408d - 「3種類」で管理するReactのState戦略 by よしこ

[^2]: https://zenn.dev/neet/articles/f25abb616ec105 - React で h1-h6 を正しく使い分ける by Ryō Igarashi

**KISS (Keep It Simple Stupid)** という名言があるように、「Props を渡すだけ」というわかりやすい方法を取ることで、将来のコード読解が楽になり、メンテナンスが容易になり、そもそもバグの混入を防げるので、一石三鳥です。

![深すぎる依存関係と浅い依存関係](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234029/7e3bfdc4-33a6-2100-40e8-a5fbe494c7e8.png)


---

２つの方法がありますが、まずは前者「**そもそも無駄なコンポーネントを挟まない**」をこの記事で解説します。

1\. **そもそも無駄なコンポーネントを挟まない ← 今ここ**

2\. コンポジション (ReactNode 型 Props) を使う

https://qiita.com/honey32/items/4d04e454550fb1ed922c

3\. ステートを適切なコンポーネントに置く

https://qiita.com/honey32/items/2e6206c7dc1974b9bf9a

このうち、3 については、すでに記事化している内容と重複するので、そちらを参照してください　（バケツリレーには言及していませんが、不要な Props の受け渡しが削られていることが明らかだと思います。）




:::note warn
この記事が示すのは方向性であり、サンプルコードを鵜呑みにするべきではありません。スタイリングに使う技術(CSS, CSS in JS, MUI のようなライブラリ, etc.) によってもベストは変わります。

もちろんですが、仕様書・デザインカンプと実際のコードをつぶさに観察しながら「この層は本当に必要なのか、そうでないのか」を検討しましょう。
:::

## Not Good 不要なレイヤーだらけ

あなたは、ブログ作成プロジェクトに新しく参画して、既存のコードを理解するために読み進めています。

まずは、**記事一覧ページ**全体のコンポーネントから初めて、リストの各要素はどのように記述されているのか見てみましょう。

```tsx:ArticleIndexPage.tsx
const ArticleIndexPage: FC = () => {
  const articles = useSWR(/* 中略 */, { suspense: false });

  return (
    <div>
      <AppHeader />
      <ArticleIndexPageMiddle
        articles={articles.data}
        totalCount={articles.totalCount}
      />
      <AppFooter />
    </div>
  );
};
```

お、ページの主要部は ArticleIndexPageMiddle ってコンポーネントに書かれてるんやな

```tsx:ArticleIndexPageMiddle.tsx
type Props = {
  totalCount: number,
  articles: Articles[],
}

const ArticleIndexPageMiddle: FC<Props> = ({
  totalCount,
  articles,
}) => {
  return (
    <main>
      <div>{totalCount}件</div>
      <ArticlesArea articles={articles} />
    </main>
  );
};
```

お？詳しくは ArticlesArea を見ればええんか

```tsx:ArticlesArea.tsx
type Props = {
  articles: Article[]
}

export const ArticlesArea: FC<Props> = ({ articles }) => {
  return (
    <div>
      {articles.map(item => (
        <ArticleItem 
          key={item.slug}
          title={item.title}
          slug={item.slug}
        />
      ))}
    </div>
  );
};
```

（ため息）

で、ArticleItem と...

```tsx:ArticleItem.tsx
type Props = {
  title: string,
  slug: string,
}

export const ArticleItem: FC<Props> = ({
  title,
  slug,
}) => {
  return (
    <article>
      <h2>{title}</h2>
      <a href={`/post/${slug}`}>読む</a>
    </article>
  );
};
```

これでやっと到達しました。

 「swr によるデータ取得 → ArticleItem の各 Props」 の **ページ全体のデータの流れ** を把握するためには、何度も何度もコードジャンプをしなければならず、「コードベースの全体像 / 詳細をともに把握する」ことが難しくなります。

![ArticleIndexPage を起点とした深すぎる依存関係のチェーン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234029/65bb307c-e982-705d-f60f-1c83ea12135f.png)


## After 無駄な中間層としてのコンポーネントを削除

新しいメンバーが困らないためには、どうすれば良かったのでしょうか？

ここでは、いらないコンポーネントの層を取り払ってしまうのが、一つの解決策としてありえます。 ( ArticleItem のように、リストの各要素のコンポーネントは **必要な抽象化** として役立つので、残してあげました。)

新しくなった ArticleIndexPage を見てみましょう。

```tsx:ArticleIndexPage.tsx
const ArticleIndexPage: FC = () => {
  const articles = useSWR(/* 中略 */, { suspense: true });

  return (
    <div>
      <AppHeader />
      <main>
        <ArticleIndexSummary totalCount={articles.totalCount} />
        <ArticleIndexItems articles={articles.items} />
      </main>
      <AppFooter />
    </div>
  );
};

export default ArticleIndexPage;
```

ずいぶん読みやすくなりましたね。ページ本体である ArticleIndexPage を見るだけで 「swr によるデータ取得して、総件数と記事一覧のコンポーネントに渡している」 という **データの流れが一望できます** し、ArticleItem の詳細な実装にたどり着くのに **必要なコードジャンプが最大で2回だけ** になります。

![浅く広くなったArticleIndexPage 起点の依存関係チェーン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234029/d4a9faa1-1117-56a1-68a1-e5fee84d5afb.png)

```tsx:ArticleIndexSummary.tsx
type Props = {
  totalCount: number,
}

export const ArticleIndexSummary: FC<Props> = () => {
  return <div>{totalCount}件</div>;
}
```

```tsx:ArticleIndexItems.tsx
type Props = {
  articles: { title: string, slug: string }[],
}

export const ArticleIndexItems: FC<Props> = ({
  articles
}) => {
  return (
    <div>
      {articles.map(item => (
        <ArticleItem 
          key={item.slug}
          title={item.title}
          slug={item.slug}
        />
      ))}
    </div>
  );
}
```

```tsx:ArticleItem.tsx
type Props = {
  title: string,
  slug: string,
}

export const ArticleItem: FC<Props> = ({
  title,
  slug,
}) => {
  return (
    <article>
      <h2>{title}</h2>
      <a href={`/post/${slug}`}>読む</a>
    </article>
  );
};
```




## まとめ

![深すぎる依存関係と浅い依存関係](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234029/7e3bfdc4-33a6-2100-40e8-a5fbe494c7e8.png)

このように、見た目上のグループをそのままコンポーネントの入れ子として実装してしまうと、余計な Props の受け渡しが生まれ、必要なコードジャンプも増えてしまいます。**フラットに書けるところはフラットに書く**ことで依存関係のチェーンを浅くすることが大切です。

「とはいっても、スタイルの都合上コンポーネントにまとめないといけなくて、どうしてもコンポーネントのネストが深くなってしまう...」という方は、 **コンポジション (composition)**　を使ってみてはどうでしょうか？

▼ 次回の記事はこちら

https://qiita.com/honey32/items/4d04e454550fb1ed922c

## 関連スライド、メモ

https://speakerdeck.com/taro28/propsnohaketuriredui-ce-teglobal-statewoshi-usonoqian-ni

https://scrapbox.io/fsubal/props_%E3%81%AE%E3%83%90%E3%82%B1%E3%83%84%E3%83%AA%E3%83%AC%E3%83%BC%E3%81%A3%E3%81%A6%E4%BD%95%E3%81%8C%E6%82%AA%E3%81%84%E3%82%93%E3%81%A0%E3%81%A3%E3%81%91

## 脚注の記事

▼ 「グローバルな状態」は実はほんの少しなのでは？という洞察を掘り進められた記事

https://zenn.dev/yoshiko/articles/607ec0c9b0408d

▼ Context はグローバルな状態というより、コンポーネントの階層構造を生かした実装に向いてる、という記事

https://zenn.dev/neet/articles/f25abb616ec105