🎨

React(TS)のユニオン×リテラル型を用いて単一コンポーネントから複数のCSSを出し分ける

2024/08/20に公開

まえがき

ReactやTypeScriptでの開発において、コンポーネントのスタイリングを効率的に管理することは重要です。特に、テーマカラーやバリエーションを持つコンポーネントの場合、スタイルの一貫性を保ちながら柔軟にカスタマイズできる仕組みが求められます。今回の記事では、TypeScriptのユニオン×リテラル型を活用して、単一コンポーネントからCSSのクラスをPropsで出し分ける実装について解説します。(tailwindを使用)

ユニオン×リテラル型の関連記事↓
https://zenn.dev/gamuprog/articles/61a43181d43bd7

コードの概要

以下のコードは僕が個人開発で作成しているブログサイトの実際のコードです。

export type ThemeColorVariant = "red" | "blue" | "green" | "orange";
type Props = {
  posts: Post[];
  className?: string;
  sectionTitleJa: string;
  sectionTitleEn: string;
  maxPostsToShow?: number;
  themeColorVariant: ThemeColorVariant;
  viewMoreTo?: string;
};

export function HomeSection({
  themeColorVariant,
  posts,
  className,
  sectionTitleJa,
  sectionTitleEn,
  maxPostsToShow = 3,
  viewMoreTo,
}: Props) {
  const textVariants: { [key in ThemeColorVariant]: string } = {
    red: "text-red-400",
    blue: "text-blue-400",
    green: "text-green-400",
    orange: "text-orange-400",
  };

  return (
    <section className={`pt-16 mx-auto px-36 ${className}`}>
      <h2 className="text-center text-xl md:text-4xl tracking-tight leading-tight">
        {sectionTitleJa}
      </h2>
      <div className={`text-center mb-8 ${textVariants[themeColorVariant]}`}>
        {sectionTitleEn}
      </div>
      <div className="grid grid-cols-1 md:grid-cols-3 md:gap-x-16 lg:gap-x-12 gap-y-20 md:gap-y-32">
        {posts.slice(0, maxPostsToShow).map((post) => (
          <PostPreview
            themeColorVariant={themeColorVariant}
            key={post.slug}
            title={post.title}
            coverImage={post.coverImage}
            date={post.date}
            tags={post.tags}
            slug={post.slug}
            excerpt={post.excerpt}
          />
        ))}
      </div>
      {viewMoreTo ? (
        <ViewMoreButton to={viewMoreTo} className="py-16" />
      ) : (
        <div className="mt-16" />
      )}
    </section>
  );
}

このコードでは、ThemeColorVariantというユニオン型を定義しています。ThemeColorVariantは、"red", "blue", "green", "orange"という4つの文字列リテラルのいずれかの値を取ることができます。この型を使って、コンポーネントが受け取るテーマカラーを制限し、許容される色のバリエーションを明確にしています。

次に、コンポーネント内でのテーマカラーに応じたCSSクラスの切り替えを行う部分を見てみましょう。

オブジェクトtextVariantsは、ThemeColorVariant型をキーとして、各テーマカラーに対応するCSSクラスを値として持つマッピングを定義しています。このマッピングを利用することで、コンポーネント内で動的にCSSクラスを切り替えることができます。

以下の部分で、themeColorVariantに応じて適切なCSSクラスを適用します。

<div className={`text-center mb-8 ${textVariants[themeColorVariant]}`}>
  {sectionTitleEn}
</div>

ここでは、themeColorVariantの値に応じて、textVariantsオブジェクトから対応するCSSクラスを取得し、それをclassNameとして適用しています。これにより、コンポーネントは受け取ったテーマカラーに応じて異なるスタイルを持つことができます。

使い方の例

この実装の具体的な使い方を見てみましょう。例えば、HomeSectionコンポーネントを使用して、異なるテーマカラーを持つセクションを表示することができます。

<HomeSection
  themeColorVariant="blue"
  posts={posts}
  sectionTitleJa="最新の記事"
  sectionTitleEn="Latest Articles"
  viewMoreTo="/blog"
/>

このように、themeColorVariantプロパティに"blue"を渡すことで、セクションのタイトルが青色で表示されます。同様に、"red", "green", "orange"を渡せば、それぞれの色に応じたスタイルが適用されます。

この実装のメリット

1. 柔軟性の向上

この実装により、コンポーネントの柔軟性が大幅に向上します。例えば、HomeSectionコンポーネントを複数の場所で使用する場合、テーマカラーに応じたスタイルの調整が容易になります。もしCSSクラスを直書きしていた場合、変更箇所が増え、タイポやスタイルの不一致が発生するリスクが高まります。しかし、ThemeColorVariantのようなユニオン型とオブジェクトマッピングを使うことで、スタイルを一括で管理・変更でき、タイポが発生した際も型チェックによってエラーとして検出できるため、コードの保守性が高まります。

2. 一貫したデザインの保持

PostPreviewのような子コンポーネントにテーマカラーのバリエーションをバケツリレーのように渡すことができる点も大きなメリットです。これにより、HomeSectionとその内部で使用されるPostPreviewのテーマが常に一致することが保証され、セクション全体で一貫したデザインを保持することができます。
これらのメリットにより、今回の実装は、スタイル管理の効率を高め、コードの安全性と保守性を強化する優れた手法となっています。

終わりに

今回の記事では、ReactとTypeScriptを使って、テーマカラーを動的に切り替える実装について解説しました。ユニオン×リテラル型とオブジェクトマッピングを活用することで、コードの可読性と保守性を高めつつ、柔軟にスタイリングを管理することができます。複数のテーマカラーを持つコンポーネントを効率的に管理したい場合、今回紹介した手法は非常に有用です。次回もTypeScriptやReactに関連するテクニックを紹介していきたいと思います。お読みいただきありがとうございました🥳

Discussion