4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

アンチパターンから学ぶReactのProps

4
Posted at

0. はじめに

この記事は、学んだ技術や知識についてを備忘録としてまとめることを目的にしている。今回はReactのPropsの基本とアンチパターンについてまとめる。

1. 開発環境

  • OS: macOS Sequoia 15.5
  • エディタ: VS Code
  • ライブラリ: React 19.2.0
  • フレームワーク: Next.js 16.0.5
    • ルーター: App Router

2. Propsとは

2.1. 定義

Propsとは、親コンポーネントから子コンポーネントへ渡すデータ構造のこと。

src/components/Child.tsx
// propsは第一引数で受け取る
// propsの名称はキャメルケースで任意に命名するのが一般的
export default function Child(props) {
  return (
    <div>
      <p>Childコンポーネント</p>
      {/* 各値にアクセスするには、props.プロパティ名と書く */}
      <p>props.one: {props.one}</p>
      <p>props.two: {props.two}</p>
      <p>props.three: {props.three}</p>
    </div>
  );
}
src/app/page.tsx
'use client';

import Child from "@/components/Child";

export default function Home() {

  return (
    <main>
      <Child
        one="親から渡された値1"
        two="親から渡された値2"
        three="親から渡された値3"
      />
    </main>
  );
}

スクリーンショット 2026-07-26 21.39.30.png

上記のone, two, threeの部分はプロパティ名またはキーprop名などと呼ばれる。

プロパティ名やキーと言われるのは、Propsがオブジェクトであることに由来する。つまり上記の例では、親(page.tsx)内でChileコンポーネントに3つの値を渡しているが、これは以下のようなオブジェクトになっている。

{
    one: "親から渡された値1",
    two: "親から渡された値2",
    three: "親から渡された値3",
{

よって、あらかじめpropsをオブジェクト表記で書くことが多い。このとき、各プロパティ名に対しての型定義を定義(propsの型定義)をする。

src/components/Child.tsx
// propsの型を定義する
type ChildProps = {
  one: string;
  two: number;
  three: boolean;
};

// propsは第一引数で受け取る
// propsの名称は任意
export default function Child({ one, two, three }: ChildProps) {
  return (
    <div>
      <p>Childコンポーネント</p>
      {/* 各値にアクセスするには、props.プロパティ名と書く */}
      <p>one(string型): {one}</p>
      <p>two(number型): {two}</p>
      <p>three(boolean型): {three.toString()}</p>
    </div>
  );
}
src/app/page.tsx
'use client';

import Child from "@/components/Child";

export default function Home() {

  return (
    <main>
      <Child
        one="親から渡された値1"
        two={42}
        three={true}
      />
    </main>
  );
}

※なお、propsにおけるオブジェクト表記のことを「Propsの分割代入(Destructuring Props)」と呼ぶことが慣例としてあります。これは中の動きを考えれば間違っていないのですが、個人的には直感的ではなく気持ち悪いので、本記事では一般的ではない「オブジェクト表記」と表現しました。

2.2. Propsにおける要点

Propsはイミュータブルである

子コンポーネントは受け取ったpropsを書き換えてはいけない。

タグで挟んだものは props.children として受け取ることができる

3. アンチパターン及び改修案の実装例

3.1. 不必要なデータまで渡す

3.1.1. アンチパターン例

コンポーネントが必要としていないデータまで含んだ、巨大なオブジェクトをそのまま渡してしまう。

3.1.2. 問題点

コンポーネントが何に依存しているのかが不明になる。

3.1.3. 解消例

必要な値だけを渡すようにする。

3.2. 状態の同期

アンチパータンから学ぶReactのuseState3.1.1. PropsをローカルStateにコピーする を参照

3.3. 値のバケツリレー

3.3.1. アンチパターン例

最も有名な問題。深くネストされた子孫コンポーネントにデータを渡すために、使用しないPropsを延々と中間コンポーネントに渡さなければならない。

src/components/Child.tsx
import Grandchild from "./Grandchild";
import { GreatGreatGrandchildProps } from "./GreatGreatGrandchild";

export default function Child({ value }: GreatGreatGrandchildProps) {
  return (
    <div>
      <p>Childコンポーネント</p>
      <Grandchild
        value={value}
      />
    </div>
  );
}
src/components/Grandchild.tsx
import GreatGrandchild from "./GreatGrandchild";
import { GreatGreatGrandchildProps } from "./GreatGreatGrandchild";

export default function Grandchild({ value }: GreatGreatGrandchildProps) {
  return (
    <div>
      <p>Grandchildコンポーネント</p>
      <GreatGrandchild 
        value={value}
      />
    </div>
  )
}
src/components/GreatGrandchild.tsx
import GreatGreatGrandchild, { GreatGreatGrandchildProps } from "./GreatGreatGrandchild";

export default function GreatGrandchild({ value }: GreatGreatGrandchildProps) {
  return (
    <div>
      <p>GreatGrandchildコンポーネント</p>
      <GreatGreatGrandchild 
        value={value}
      />
    </div>
  )
}
src/components/GreatGreatGrandchild.tsx
export type GreatGreatGrandchildProps = {
  value: string;
};

export default function GreatGreatGrandchild({ value }: GreatGreatGrandchildProps) {
  return (
    <div>
      <p>GreatGreatGrandchildコンポーネント</p>
      <p>親から渡された値:{value}</p>
    </div>
  )
}
src/app/page.tsx
'use client';

import Child from "@/components/Child";

export default function Home() {

  return (
    <main>
      <Child
        value="GreatGreatGrandchildに渡す値"
      />
    </main>
  );
}

スクリーンショット 2026-07-26 23.51.01.png

3.3.2. 問題点

中間コンポーネントが不要なPropsに依存してしまい、冗長なだけではなく再利用生や保守性が低下する。

3.3.3. 解消例その1

コンポーネントの合成を用いてPropsのバケツリレーを解消する。React標準のchildrenPropを用いる。

src/components/Child.tsx
import { ReactNode } from "react";

export default function Child({ children }: { children: ReactNode }) {
  return (
    <div>
      <p>Childコンポーネント</p>
      {children}
    </div>
  );
}

アンチパターンではChildコンポーネントは自分自身で Grandchildをインポートし、JSXの中にハードコードしていた。しかしchildrenを使うと、「中に何が入るか」の決定権を親コンポーネントに委ねることができる。

そもそも中間コンポーネントにおいて「配下に何が来るのか」、「どんなデータが必要なのか」を知る必要がなくなり、再利用しやすい汎用的なコンポーネントとなっている。

以下コンポーネントのchildrenも同様。

src/components/Grandchild.tsx
import { ReactNode } from "react";

export default function Grandchild({ children }: { children: ReactNode }) {
  return (
    <div>
      <p>Grandchildコンポーネント</p>
      {children}
    </div>
  );
}
src/components/GreatGrandchild.tsx
import { ReactNode } from "react";

export default function GreatGrandchild({ children }: { children: ReactNode }) {
  return (
    <div>
      <p>GreatGrandchildコンポーネント</p>
      {children}
    </div>
  )
}
src/components/GreatGreatGrandchild.tsx
// 最後の子孫コンポーネントのみ変更無し
export type GreatGreatGrandchildProps = {
  value: string;
};

export default function GreatGreatGrandchild({ value }: GreatGreatGrandchildProps) {
  return (
    <div>
      <p>GreatGreatGrandchildコンポーネント</p>
      <p>親から渡された値:{value}</p>
    </div>
  )
}
src/app/page.tsx
'use client';

import Child from "@/components/Child";
import Grandchild from "@/components/Grandchild";
import GreatGrandchild from "@/components/GreatGrandchild";
import GreatGreatGrandchild from "@/components/GreatGreatGrandchild";

export default function Home() {
  return (
    <main>
      <Child>
        <Grandchild>
          <GreatGrandchild>
            <GreatGreatGrandchild value="GreatGreatGrandchildに渡す値" />
          </GreatGrandchild>
        </Grandchild>
      </Child>
    </main>
  );
}

これにより依存関係が可視化され、page.tsxを見るだけでDOMの階層構造と、どのコンポーネントがどのデータを必要としているかがひと目で分かる。

またContext APIを使う場合、Contextの値が更新されるとConsumer配下が全て再レンダリングされるリスクがあるが、この合成アプローチであれば不必要な再レンダリングを自然に防ぐことが可能。

3.3.4. 解消例その2

Context APIを用いる※後日追記

参考資料

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?