0
0

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でやるCompound Patternの例・メリット・デメリット

Posted at

コード例

ヘッダー(タグ子コンポーネントとして持つ)、本文、を要素として持つArticleというコンポーネントを作ってみる

Article.tsx
  interface ArticleComposition {
    Header: React.FC<ArticleHeaderProps>;
    Body: React.FC<ArticleBodyProps>;
    HeaderTag: React.FC<ArticleHeaderTagProps>;
  }

  interface ArticleProps {
    children: React.ReactNode;
  }
  interface ArticleHeaderProps {
    title: string;
    children: React.ReactNode;
  }
  interface ArticleHeaderTagProps {
    tag: string;
  }
  interface ArticleBodyProps {
    content: string;
  }
  
  
  export const Article: React.FC<ArticleProps> & ArticleComposition = ({ children }) => {
    return <>{children}</>;
  };
  const ArticleHeader: React.FC<ArticleHeaderProps> = ({ children, title }) => {
    return (
        <div>
            <h2>{title}</h2>
            <div>{children}</div>
        </div>
    );
  };
  const ArticleHeaderTag: React.FC<ArticleHeaderTagProps> = ({ tag }) => {
    return <div>{tag}</div>;
  };
  const ArticleBody: React.FC<ArticleBodyProps> = ({ content }) => {
    return <div>{content}</div>;
  };
  
  Article.Header = ArticleHeader;
  Article.HeaderTag = ArticleHeaderTag;
  Article.Body = ArticleBody;
使用例
<Article>
  <Article.Header title="記事タイトル">
    <Article.HeaderTag tag="技術" />
  </Article.Header>
  <Article.Body content="記事の本文内容..." />
</Article>

解説

ArticleComposition インターフェース
interface ArticleComposition {
  Header: React.FC<ArticleHeaderProps>;
  Body: React.FC<ArticleBodyProps>;
  HeaderTag: React.FC<ArticleHeaderTagProps>;
}

上記は、Articleコンポーネントが持つサブコンポーネントの構成を定義しています。

  • Header:ArticleHeaderPropsをpropsとして持つコンポーネント
  • Body:ArticleBodyPropsをpropsとして持つコンポーネント
    HeaderTag:ArticleHeaderTagPropsをpropsとして持つコンポーネント
  interface ArticleProps {
    children: React.ReactNode;
  }
  interface ArticleHeaderProps {
    title: string;
    children: React.ReactNode;
  }
  interface ArticleHeaderTagProps {
    tag: string;
  }
  interface ArticleBodyProps {
    content: string;
  }

上記は、各コンポーネントのProps定義を行っています。

  export const Article: React.FC<ArticleProps> & ArticleComposition = ({ children }) => {
    return <>{children}</>;
  };
  const ArticleHeader: React.FC<ArticleHeaderProps> = ({ children, title }) => {
    return (
        <div>
            <h2>{title}</h2>
            <div>{children}</div>
        </div>
    );
  };
  const ArticleHeaderTag: React.FC<ArticleHeaderTagProps> = ({ tag }) => {
    return <div>{tag}</div>;
  };
  const ArticleBody: React.FC<ArticleBodyProps> = ({ content }) => {
    return <div>{content}</div>;
  };

上記部分では、コンポーネントを実装しています:

  • 特に、Articleの型定義は、React.ReactNode & React.FC<ArticleHeaderTagProps>という少し複雑な型定義です
  • この&は、インターセクション型と呼ばれ、両方の型の特性を持つ必要があることを示します
  • つまりは、Header、Body、HeaderTagをサブコンポーネントとして持つ、FC<ArticleProps>型のコンポーネントということを示します
Article.Header = ArticleHeader;
Article.HeaderTag = ArticleHeaderTag;
Article.Body = ArticleBody;

上記の部分では、サブコンポーネントをメインのArticleコンポーネントに割り当てています

どういう時に有効と感じるか

私は、孫コンポーネント以降の情報が適度に可視化されることに特に利点があると感じます。
例として、Compound Patternを使わずに、単にコンポーネント分割させた場合を見てみます。

使用例
  const MyArticle = () => (
    <Article
      title="Reactの最新機能"
      tag="フロントエンド"
      content="Reactの最新バージョンでは..."
    />
  );
Articleの各コンポーネントの詳細はコチラ
ArticleHeaderコンポーネントの定義
  interface ArticleHeaderProps {
    title: string;
    tag: string;
  }
  const ArticleHeader: React.FC<ArticleHeaderProps> = ({ title, tag }) => (
    <div className="article-header">
      <h2>{title}</h2>
      <ArticleTag tag={tag} />
    </div>
  );
ArticleTagコンポーネントの定義
  interface ArticleTagProps {
    tag: string;
  }
  const ArticleTag: React.FC<ArticleTagProps> = ({ tag }) => (
    <div className="article-tag">{tag}</div>
  );
ArticleBodyコンポーネントの定義
  interface ArticleBodyProps {
    content: string;
  }
  const ArticleBody: React.FC<ArticleBodyProps> = ({ content }) => (
    <div className="article-body">{content}</div>
  );
Articleコンポーネントの定義
  interface ArticleProps {
    title: string;
    tag: string;
    content: string;
  }
  const Article: React.FC<ArticleProps> = ({ title, tag, content }) => {
    return (
      <div className="article">
        <ArticleHeader title={title} tag={tag} />
        <ArticleBody content={content} />
      </div>
    );
  };

上記のように、使用時に、Articleの内部構造が隠蔽されてしまいます。
Articleがどのような子コンポーネントで構成されているかが、明確ではありません

Compound Patternのその他メリット

  1. 柔軟な構成:
    • 必要に応じて、コンポーネントを自由に追加、削除、または並べ替えることができる
    • これにより、異なるシナリオに対応しやすくる
  2. デバッグの容易さ:
    • コンポーネントの階層構造が明確なため、問題が発生した際にどのコンポーネントが関係しているかを特定しやすくなる
  3. propsバケツリレーの回避:
    • 深くネストされたpropsのバケツリレーを避けることができる
  4. カスタマイズの柔軟性:
    • 必要なコンポーネントのみを使用し、不要なものを省略できる
Compound Patternを使わない時のデメリット

使う時のメリットの裏返しにはなりますが、複雑なコンポーネントでCompound Patternを使わない時に発生しうるデメリットです。

  1. 構造の固定:

    • Articleコンポーネント内で構造が決定されているため、使用時に順序や構成を変更することが難しい
  2. propsのバケツリレー:

    • 親コンポーネントから子コンポーネントへpropsを渡す必要があり、中間コンポーネントを経由してpropsが伝播される
  3. カスタマイズの制限:

    • 各部分に対して個別のpropsや子要素を追加することが難しくなる
  4. 明示的な構造:

    • 使用時に、Articleの内部構造が隠蔽されており、どのような子コンポーネントで構成されているかが明確でなくなる

デメリットは?

以下のようなデメリットがあるみたいです。
この辺りの話はまだまだ勉強中なので、別の機会に記事化しようと思います。

  1. パフォーマンス最適化の難しさ
    • コンテキスト依存性:子コンポーネントが親のコンテキストに依存
    • プロパティ注入メカニズム:React.cloneElementによる動的プロパティ付与
    • レンダリング連鎖:親の状態変化が子コンポーネント群全体に影響
  2. React.Children.map()React.cloneElement()を使う時に複雑性や制限が出てくる
    • コンポーネント階層の制限
    • プロパティ管理の複雑性
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?