ReactとTypeScriptで実現する型安全なカスタムテーマの設計方法

Reactアプリケーションにおいて、テーマの一貫性を保つことは、デザインの統一性を高め、ユーザー体験を向上させるために欠かせません。しかし、単なるテーマ設計ではなく、型安全性を備えたカスタムテーマを導入することで、開発者の生産性やコードの保守性が大きく向上します。本記事では、TypeScriptを活用して、型安全かつ拡張性の高いカスタムテーマをReactアプリに組み込む方法について解説します。基礎から実践的な応用例までを通じて、すぐに活用できるスキルを身に付けましょう。

目次

型安全なテーマ設計の必要性


Reactでテーマを導入する際、型安全性を確保することは、開発効率の向上とバグの削減に直結します。型安全な設計を行うことで、開発者は以下のメリットを享受できます。

コーディング時の安心感


TypeScriptを用いてテーマを型定義することで、プロパティ名の入力ミスや型の不整合をコンパイル時に検出できます。これにより、ランタイムエラーを未然に防ぐことが可能です。

自動補完機能の活用


型情報が明示されていると、コードエディタでの自動補完機能が強力にサポートされ、テーマプロパティの正しい利用方法を簡単に確認できます。これにより、学習曲線の緩和やスムーズな開発が実現します。

再利用性と保守性の向上


型安全なテーマ設計は、プロジェクトが大規模化した際にも一貫性を保ちやすく、他の開発者がテーマを拡張または変更する際の負担を軽減します。

型安全でないテーマ設計のリスク


型定義を省略したテーマでは、意図しない値がプロパティに設定される可能性が高まり、ランタイムでの予期せぬ動作やデザイン崩れの原因となります。特に、多くの開発者が関わるプロジェクトでは、このリスクが顕著です。

型安全性を備えたテーマ設計は、Reactプロジェクトの品質と開発効率を大きく向上させる重要な手法です。次章では、TypeScriptを活用したテーマ定義の基本について解説します。

TypeScriptを使ったテーマ定義の基本

Reactアプリで型安全なテーマを設計するためには、TypeScriptを活用してテーマ構造を定義します。このセクションでは、基本的なテーマの型定義方法と実際の活用例について解説します。

テーマの型定義


まず、テーマの型をTypeScriptで定義します。テーマは色、フォント、スペーシングなどのプロパティを含むオブジェクトとして構成されます。以下は基本的な型定義の例です。

// Themeの型を定義
export interface Theme {
  colors: {
    primary: string;
    secondary: string;
    background: string;
    text: string;
  };
  fontSizes: {
    small: string;
    medium: string;
    large: string;
  };
  spacing: (factor: number) => string;
}

テーマオブジェクトの作成


型定義を基に、具体的なテーマオブジェクトを作成します。

export const lightTheme: Theme = {
  colors: {
    primary: "#6200ea",
    secondary: "#03dac6",
    background: "#ffffff",
    text: "#000000",
  },
  fontSizes: {
    small: "12px",
    medium: "16px",
    large: "20px",
  },
  spacing: (factor) => `${factor * 8}px`,
};

テーマをコンポーネントで活用する


作成したテーマをReactコンポーネントで利用する際には、テーマの型を用いて安全性を確保します。

import React from "react";
import { Theme } from "./theme";

interface ButtonProps {
  theme: Theme;
}

const Button: React.FC<ButtonProps> = ({ theme }) => {
  return (
    <button
      style={{
        backgroundColor: theme.colors.primary,
        color: theme.colors.text,
        padding: theme.spacing(2),
        fontSize: theme.fontSizes.medium,
      }}
    >
      Click Me
    </button>
  );
};

型安全性の恩恵

  • 開発者が間違ったプロパティを利用しようとした場合、コンパイルエラーが発生し即座に修正が可能です。
  • テーマの変更や拡張が容易で、保守性が向上します。

次章では、Reactコンテキストを活用したテーマプロバイダーの実装方法について詳しく解説します。

テーマプロバイダーの実装

Reactアプリでテーマを動的に切り替えるには、Reactコンテキストを使用してテーマを管理する「テーマプロバイダー」を実装します。このセクションでは、テーマプロバイダーをゼロから作成する手順を解説します。

テーマコンテキストの作成


まず、テーマとその操作方法を格納するコンテキストを定義します。

import React, { createContext, useContext, useState, ReactNode } from "react";
import { lightTheme, Theme } from "./theme";

// コンテキストの型定義
interface ThemeContextType {
  theme: Theme;
  toggleTheme: () => void;
}

// 初期値を設定
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

テーマプロバイダーの実装


テーマの状態を管理し、アプリ全体に提供するプロバイダーを作成します。

import { darkTheme } from "./theme";

export const ThemeProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
  const [theme, setTheme] = useState<Theme>(lightTheme);

  const toggleTheme = () => {
    setTheme((prevTheme) => (prevTheme === lightTheme ? darkTheme : lightTheme));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

カスタムフックでテーマを利用する


コンテキストを直接使用する代わりに、カスタムフックを用意することでコードの簡潔性を向上させます。

export const useTheme = () => {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error("useTheme must be used within a ThemeProvider");
  }
  return context;
};

アプリへのテーマプロバイダーの適用


アプリのルートコンポーネントでテーマプロバイダーを適用します。

import React from "react";
import { ThemeProvider } from "./ThemeProvider";
import App from "./App";

const Root: React.FC = () => {
  return (
    <ThemeProvider>
      <App />
    </ThemeProvider>
  );
};

export default Root;

テーマの切り替えを使用する


コンポーネントでテーマとテーマ切り替え機能を利用します。

import React from "react";
import { useTheme } from "./ThemeProvider";

const ThemeSwitcher: React.FC = () => {
  const { theme, toggleTheme } = useTheme();

  return (
    <div style={{ backgroundColor: theme.colors.background, color: theme.colors.text }}>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
};

export default ThemeSwitcher;

まとめ

  • テーマプロバイダーを実装することで、アプリ全体でテーマを一元管理可能になります。
  • ReactコンテキストとTypeScriptの型安全性を組み合わせることで、より堅牢なテーマ切り替え機能が構築できます。

次章では、styled-componentsを使用して型安全なテーマをコンポーネントに適用する方法について説明します。

styled-componentsでテーマを適用する方法

Reactアプリでテーマを適用する際に、styled-componentsを使うと便利です。さらにTypeScriptの型安全性を組み合わせることで、エラーを防ぎながらテーマを効率的に利用できます。このセクションでは、styled-componentsでテーマを適用する方法を解説します。

styled-componentsのセットアップ


まず、必要なライブラリをインストールします。

npm install styled-components
npm install @types/styled-components --save-dev

テーマの型定義を拡張


styled-componentsではDefaultTheme型を拡張してテーマの型情報を設定します。

// styled.d.ts
import "styled-components";
import { Theme } from "./theme";

declare module "styled-components" {
  export interface DefaultTheme extends Theme {}
}

テーマを提供する


テーマプロバイダーを使ってアプリにテーマを提供します。

import React from "react";
import { ThemeProvider as StyledThemeProvider } from "styled-components";
import { useTheme } from "./ThemeProvider";

export const AppThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const { theme } = useTheme();

  return <StyledThemeProvider theme={theme}>{children}</StyledThemeProvider>;
};

テーマを使用したスタイル定義


styled-componentsでテーマを利用するには、props.themeを参照します。

import styled from "styled-components";

const Button = styled.button`
  background-color: ${(props) => props.theme.colors.primary};
  color: ${(props) => props.theme.colors.text};
  padding: ${(props) => props.theme.spacing(2)};
  font-size: ${(props) => props.theme.fontSizes.medium};
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    background-color: ${(props) => props.theme.colors.secondary};
  }
`;

コンポーネントでテーマを利用する


テーマが適用されたスタイルを使用してコンポーネントを作成します。

import React from "react";
import Button from "./Button";

const ThemedComponent: React.FC = () => {
  return (
    <div>
      <h1>Welcome to the Themed App</h1>
      <Button>Click Me</Button>
    </div>
  );
};

export default ThemedComponent;

全体の統合


アプリ全体でテーマが適用されるように、AppThemeProviderをルートに設定します。

import React from "react";
import ReactDOM from "react-dom";
import { ThemeProvider } from "./ThemeProvider";
import App from "./App";

ReactDOM.render(
  <ThemeProvider>
    <App />
  </ThemeProvider>,
  document.getElementById("root")
);

styled-componentsのメリット

  1. テーマの一元管理: 色やフォントサイズなどを集中管理できます。
  2. ダイナミックなスタイル: プロパティに基づいて動的なスタイルを簡単に適用できます。
  3. 型安全性: TypeScriptでエラーを事前に防ぐことが可能です。

次章では、Material-UIとTypeScriptを使用してカスタムテーマをさらに深く活用する方法について解説します。

Material-UIとTypeScriptでテーマをカスタマイズする方法

Material-UI(現在のMUI)は、Reactアプリで洗練されたUIを構築するためのライブラリです。TypeScriptを使用してMaterial-UIのテーマをカスタマイズすることで、型安全性を確保しつつ、独自のデザイン要件に対応できます。このセクションでは、Material-UIでのテーマカスタマイズの基本から実践までを解説します。

Material-UIのインストール


必要なパッケージをインストールします。

npm install @mui/material @emotion/react @emotion/styled
npm install @mui/icons-material

テーマの型定義


Material-UIではテーマをカスタマイズするためにTheme型を拡張します。

// theme.ts
import { createTheme } from "@mui/material/styles";

// 独自のテーマ定義
const customTheme = createTheme({
  palette: {
    primary: {
      main: "#6200ea",
    },
    secondary: {
      main: "#03dac6",
    },
    background: {
      default: "#ffffff",
    },
    text: {
      primary: "#000000",
    },
  },
  typography: {
    fontSize: 14,
    fontFamily: "'Roboto', 'Helvetica', 'Arial', sans-serif",
  },
  spacing: 8, // ベースのスペーシング単位
});

export default customTheme;

テーマプロバイダーの適用


Material-UIのThemeProviderを使用してテーマをアプリに適用します。

import React from "react";
import { ThemeProvider } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import customTheme from "./theme";

const AppThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  return (
    <ThemeProvider theme={customTheme}>
      <CssBaseline />
      {children}
    </ThemeProvider>
  );
};

export default AppThemeProvider;

カスタムテーマを利用したコンポーネントの作成


Material-UIのコンポーネントをテーマに基づいてスタイリングします。

import React from "react";
import { Button, Typography } from "@mui/material";

const ThemedComponent: React.FC = () => {
  return (
    <div style={{ padding: "16px" }}>
      <Typography variant="h4" color="primary">
        Welcome to the Themed App
      </Typography>
      <Button variant="contained" color="primary" style={{ marginTop: "16px" }}>
        Click Me
      </Button>
    </div>
  );
};

export default ThemedComponent;

テーマの切り替え機能を追加


テーマ切り替えをサポートするには、createThemeを利用してダークモード用とライトモード用のテーマを準備します。

// themeSwitcher.ts
import { createTheme } from "@mui/material/styles";

export const lightTheme = createTheme({
  palette: {
    mode: "light",
    primary: { main: "#6200ea" },
    secondary: { main: "#03dac6" },
  },
});

export const darkTheme = createTheme({
  palette: {
    mode: "dark",
    primary: { main: "#bb86fc" },
    secondary: { main: "#03dac6" },
  },
});

テーマを切り替えるロジックを追加します。

import React, { useState } from "react";
import { ThemeProvider } from "@mui/material/styles";
import { lightTheme, darkTheme } from "./themeSwitcher";
import CssBaseline from "@mui/material/CssBaseline";

const ThemeSwitcherProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [isDarkMode, setIsDarkMode] = useState(false);

  const toggleTheme = () => setIsDarkMode(!isDarkMode);

  return (
    <ThemeProvider theme={isDarkMode ? darkTheme : lightTheme}>
      <CssBaseline />
      <button onClick={toggleTheme} style={{ position: "absolute", top: "10px", right: "10px" }}>
        Toggle Theme
      </button>
      {children}
    </ThemeProvider>
  );
};

export default ThemeSwitcherProvider;

まとめ


Material-UIとTypeScriptを活用すれば、柔軟で型安全なカスタムテーマを簡単に設計できます。テーマの切り替え機能を追加することで、ユーザー体験を向上させるデザインを実現しましょう。

次章では、ダークモードとライトモードを切り替える実践的なテーマ切り替え機能の具体的な実装について解説します。

実践的なテーマ切り替え機能の実装例

テーマ切り替えは、ユーザー体験を向上させるために多くのアプリケーションで取り入れられています。このセクションでは、ダークモードとライトモードを切り替える機能を実践的に実装する方法を紹介します。

テーマ定義の準備


ダークモードとライトモードのテーマをTypeScriptで定義します。

// themes.ts
import { createTheme } from "@mui/material/styles";

export const lightTheme = createTheme({
  palette: {
    mode: "light",
    primary: { main: "#6200ea" },
    secondary: { main: "#03dac6" },
    background: { default: "#ffffff", paper: "#f5f5f5" },
    text: { primary: "#000000", secondary: "#555555" },
  },
});

export const darkTheme = createTheme({
  palette: {
    mode: "dark",
    primary: { main: "#bb86fc" },
    secondary: { main: "#03dac6" },
    background: { default: "#121212", paper: "#1e1e1e" },
    text: { primary: "#ffffff", secondary: "#bbbbbb" },
  },
});

テーマ管理コンテキストの作成


テーマの切り替え機能を管理するReactコンテキストを作成します。

import React, { createContext, useContext, useState, ReactNode } from "react";
import { ThemeProvider } from "@mui/material/styles";
import { lightTheme, darkTheme } from "./themes";

interface ThemeContextType {
  isDarkMode: boolean;
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export const ThemeProviderWrapper: React.FC<{ children: ReactNode }> = ({ children }) => {
  const [isDarkMode, setIsDarkMode] = useState(false);

  const toggleTheme = () => setIsDarkMode((prev) => !prev);

  return (
    <ThemeContext.Provider value={{ isDarkMode, toggleTheme }}>
      <ThemeProvider theme={isDarkMode ? darkTheme : lightTheme}>
        {children}
      </ThemeProvider>
    </ThemeContext.Provider>
  );
};

export const useThemeContext = () => {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error("useThemeContext must be used within a ThemeProviderWrapper");
  }
  return context;
};

テーマ切り替えボタンの作成


切り替え機能を提供するボタンコンポーネントを作成します。

import React from "react";
import { useThemeContext } from "./ThemeProviderWrapper";
import { Button } from "@mui/material";

const ThemeToggleButton: React.FC = () => {
  const { isDarkMode, toggleTheme } = useThemeContext();

  return (
    <Button
      onClick={toggleTheme}
      variant="contained"
      color="secondary"
      style={{ position: "fixed", top: 16, right: 16 }}
    >
      {isDarkMode ? "Switch to Light Mode" : "Switch to Dark Mode"}
    </Button>
  );
};

export default ThemeToggleButton;

アプリへの統合


テーマ管理をアプリ全体に統合し、テーマ切り替えボタンを配置します。

import React from "react";
import ReactDOM from "react-dom";
import { ThemeProviderWrapper } from "./ThemeProviderWrapper";
import ThemeToggleButton from "./ThemeToggleButton";
import App from "./App";

const Root: React.FC = () => {
  return (
    <ThemeProviderWrapper>
      <App />
      <ThemeToggleButton />
    </ThemeProviderWrapper>
  );
};

ReactDOM.render(<Root />, document.getElementById("root"));

テーマの適用例


アプリケーション内でMaterial-UIのコンポーネントがテーマ設定に応じてスタイリングされます。

import React from "react";
import { Typography, Paper } from "@mui/material";

const App: React.FC = () => {
  return (
    <Paper style={{ padding: "16px", margin: "16px" }}>
      <Typography variant="h4">Themed Application</Typography>
      <Typography variant="body1">
        This application supports both Light and Dark themes. Switch themes using the button in the top-right corner.
      </Typography>
    </Paper>
  );
};

export default App;

結果


テーマ切り替えボタンをクリックするだけで、アプリ全体のカラースキームがリアルタイムで切り替わります。ユーザーはライトモードとダークモードを簡単に選択でき、快適な使用体験を得られます。

まとめ


テーマ切り替え機能を実装することで、ユーザーの好みに応じた柔軟なインターフェースを提供できます。型安全性を保ちながら設計することで、保守性の高いコードを実現できる点もポイントです。次章では、テーマ設計のベストプラクティスについてさらに深掘りします。

テーマ設計のベストプラクティス

テーマ設計は、アプリケーションのデザイン一貫性を維持しつつ、開発者の作業効率を高めるための重要な要素です。ここでは、Reactアプリケーションにおけるテーマ設計のベストプラクティスを解説します。

1. 再利用可能なデザイントークンの定義


テーマ設計では、デザイントークン(色、フォントサイズ、スペーシングなど)を再利用可能な形で定義することが推奨されます。

  • 例: カラーパレットの標準化
    色をprimarysecondaryerrorなどの抽象化された名前で管理すると、デザイン変更時に修正箇所を最小限に抑えられます。
export const themeColors = {
  primary: "#6200ea",
  secondary: "#03dac6",
  error: "#d32f2f",
};
  • デザイントークンの型定義
    TypeScriptで型を定義し、すべてのトークンが正しく利用されるようにします。
export interface ThemeTokens {
  colors: {
    primary: string;
    secondary: string;
    error: string;
  };
  spacing: (factor: number) => string;
}

2. コンポーネント間での一貫性を維持


すべてのコンポーネントで同じテーマ設定を共有するようにします。これにより、カラーパレットやフォントサイズなどが統一され、ユーザー体験が向上します。

  • 統一されたスタイルを適用する例
    ButtonやTypographyなどのベースコンポーネントでテーマを直接活用します。
import { styled } from "@mui/material/styles";

const ThemedButton = styled("button")(({ theme }) => ({
  backgroundColor: theme.palette.primary.main,
  color: theme.palette.text.primary,
  padding: theme.spacing(2),
  border: "none",
  borderRadius: "4px",
  cursor: "pointer",
}));

3. ダイナミックなテーマ切り替えを意識


ライトモードとダークモードのように、テーマがダイナミックに切り替わる場合を考慮して設計します。

  • 例: トークンの拡張
    基本のトークンをベースに、モードごとに拡張されたテーマを作成します。
const baseTheme = {
  typography: { fontSize: 14 },
  spacing: (factor: number) => `${factor * 8}px`,
};

export const lightTheme = {
  ...baseTheme,
  colors: { background: "#ffffff", text: "#000000" },
};

export const darkTheme = {
  ...baseTheme,
  colors: { background: "#121212", text: "#ffffff" },
};

4. テーマ拡張性を確保


将来的なデザイン変更や機能追加に対応するため、テーマ設計を拡張可能にすることが重要です。

  • モジュール化
    カラーパレットやタイポグラフィなど、テーマを複数のモジュールに分割します。
import { colors } from "./colors";
import { typography } from "./typography";

export const theme = { colors, typography };
  • 新規プロパティの追加
    createThemeを活用してカスタムプロパティを追加します。
import { createTheme } from "@mui/material/styles";

const customTheme = createTheme({
  palette: {
    primary: { main: "#6200ea" },
  },
  customProperties: {
    borderRadius: "8px",
  },
});

5. デバッグとテスト


テーマ設定のバグを防ぐため、以下のポイントを意識します。

  • Storybookでのプレビュー
    コンポーネントがテーマに応じて正しく動作するか確認します。
  • ユニットテストの導入
    テーマの切り替えやプロパティの適用が期待通りに動作するかをテストします。
import { render } from "@testing-library/react";
import { ThemeProvider } from "@mui/material/styles";
import { lightTheme } from "./themes";
import Button from "./Button";

test("Button applies the correct theme colors", () => {
  const { getByText } = render(
    <ThemeProvider theme={lightTheme}>
      <Button>Click Me</Button>
    </ThemeProvider>
  );
  const button = getByText("Click Me");
  expect(button).toHaveStyle("background-color: #6200ea");
});

まとめ


テーマ設計では、再利用性、一貫性、拡張性を意識することが重要です。型安全性を確保し、ダイナミックなテーマ切り替えにも対応することで、メンテナンス性の高いアプリケーションを構築できます。次章では、トラブルシューティングとデバッグ方法について解説します。

トラブルシューティングとデバッグ方法

テーマ設計や実装時には、予期しない挙動やエラーに遭遇することがあります。このセクションでは、よくある問題とその解決方法、デバッグテクニックを解説します。

1. よくある問題

1.1 テーマが正しく適用されない


原因: テーマプロバイダーの設定ミスや、テーマの型が一致していない場合に発生します。

解決方法:

  • テーマプロバイダーがアプリ全体をラップしているか確認します。
  • テーマ型がstyled-componentsやMaterial-UIの期待する型と一致しているか検証します。

コード例:

import { ThemeProvider } from "@mui/material/styles";
import { lightTheme } from "./theme";

const App = () => (
  <ThemeProvider theme={lightTheme}>
    <YourComponent />
  </ThemeProvider>
);

1.2 型エラーが発生する


原因: テーマ定義がDefaultThemeまたはTheme型に正しく統合されていない可能性があります。

解決方法:

  • styled.d.tscreateThemeの型定義を再確認し、不足や不整合を修正します。

コード例:

import "styled-components";
import { Theme } from "./theme";

declare module "styled-components" {
  export interface DefaultTheme extends Theme {}
}

1.3 ダイナミックなテーマ切り替えが機能しない


原因: コンテキストや状態管理が正しく設定されていない場合があります。

解決方法:

  • テーマ切り替え用の関数が正しく状態を更新しているか確認します。
  • コンテキストの値が予期通りに渡されているか検証します。

コード例:

const toggleTheme = () => setIsDarkMode((prev) => !prev);

2. デバッグテクニック

2.1 開発者ツールを活用する

  • React Developer Tools: コンテキスト値やプロバイダーの設定状態を確認します。
  • ブラウザの要素検証: 適用されたCSSがテーマの値に基づいているか確認します。

2.2 コンソールログでテーマ状態を確認


テーマの状態が意図通りに切り替わっているかをログ出力で確認します。

useEffect(() => {
  console.log("Current theme:", isDarkMode ? "Dark" : "Light");
}, [isDarkMode]);

2.3 スナップショットテスト


スナップショットテストを使用して、テーマに基づくUIの変化が正しいかを検証します。

import { render } from "@testing-library/react";
import { ThemeProvider } from "@mui/material/styles";
import { darkTheme } from "./theme";

test("Renders correctly in dark mode", () => {
  const { asFragment } = render(
    <ThemeProvider theme={darkTheme}>
      <YourComponent />
    </ThemeProvider>
  );
  expect(asFragment()).toMatchSnapshot();
});

3. 問題解決のヒント

3.1 テーマプロバイダーの階層を整理する

  • 複数のテーマプロバイダーが存在する場合、どのテーマが適用されているか追跡します。
  • コンポーネントのテーマ適用順序を整理し、不要なプロバイダーを削除します。

3.2 型の明示を徹底する

  • すべてのテーマプロパティに型を定義し、エディタの補完機能を活用します。

まとめ


テーマのトラブルシューティングは、設定の一貫性と型安全性を確保することで効果的に行えます。デバッグツールやテストを活用し、問題を早期に特定して解決しましょう。次章では、テーマ設計の応用例について解説します。

応用例:複数ブランド向けテーマ設計

複数ブランドや製品ラインを展開するアプリケーションでは、各ブランドに応じたテーマの切り替えが必要です。このセクションでは、複数ブランド向けのテーマ設計をどのように行うかを解説します。

1. ブランド別のテーマ定義


ブランドごとにテーマを作成し、一元管理できる構造にします。

import { createTheme } from "@mui/material/styles";

// ブランドAのテーマ
export const brandATheme = createTheme({
  palette: {
    primary: { main: "#ff5733" },
    secondary: { main: "#33c1ff" },
    background: { default: "#ffffff" },
  },
  typography: {
    fontFamily: "'Roboto', sans-serif",
  },
});

// ブランドBのテーマ
export const brandBTheme = createTheme({
  palette: {
    primary: { main: "#33ff57" },
    secondary: { main: "#ff33a1" },
    background: { default: "#f5f5f5" },
  },
  typography: {
    fontFamily: "'Arial', sans-serif",
  },
});

2. テーマ管理コンテキストの実装


ブランドごとのテーマを動的に切り替えるためにコンテキストを利用します。

import React, { createContext, useContext, useState, ReactNode } from "react";
import { ThemeProvider } from "@mui/material/styles";
import { brandATheme, brandBTheme } from "./themes";

interface BrandContextType {
  currentBrand: string;
  setBrand: (brand: string) => void;
}

const BrandContext = createContext<BrandContextType | undefined>(undefined);

export const BrandProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
  const [currentBrand, setCurrentBrand] = useState("BrandA");

  const theme = currentBrand === "BrandA" ? brandATheme : brandBTheme;

  return (
    <BrandContext.Provider value={{ currentBrand, setBrand: setCurrentBrand }}>
      <ThemeProvider theme={theme}>{children}</ThemeProvider>
    </BrandContext.Provider>
  );
};

export const useBrandContext = () => {
  const context = useContext(BrandContext);
  if (!context) {
    throw new Error("useBrandContext must be used within a BrandProvider");
  }
  return context;
};

3. ブランド切り替えUIの作成


ブランドを選択するためのUIを作成します。

import React from "react";
import { useBrandContext } from "./BrandProvider";
import { Button } from "@mui/material";

const BrandSwitcher: React.FC = () => {
  const { currentBrand, setBrand } = useBrandContext();

  return (
    <div>
      <Button
        variant="contained"
        onClick={() => setBrand("BrandA")}
        disabled={currentBrand === "BrandA"}
      >
        Switch to Brand A
      </Button>
      <Button
        variant="contained"
        onClick={() => setBrand("BrandB")}
        disabled={currentBrand === "BrandB"}
      >
        Switch to Brand B
      </Button>
    </div>
  );
};

export default BrandSwitcher;

4. 複数ブランドテーマの統合


アプリ全体でブランドテーマを利用できるよう統合します。

import React from "react";
import ReactDOM from "react-dom";
import { BrandProvider } from "./BrandProvider";
import App from "./App";
import BrandSwitcher from "./BrandSwitcher";

const Root: React.FC = () => {
  return (
    <BrandProvider>
      <BrandSwitcher />
      <App />
    </BrandProvider>
  );
};

ReactDOM.render(<Root />, document.getElementById("root"));

5. ブランド間のデザイン一貫性を維持


ブランドごとに独自のデザインを持たせながらも、ベースのデザインシステムは統一するのがベストです。

  • ベーステーマの共有: カラーパレットやタイポグラフィは共通化し、各ブランド用にカスタマイズ可能なオーバーライドを作成します。
  • レスポンシブデザイン: すべてのブランドテーマで一貫したレスポンシブ設計を適用します。

まとめ


複数ブランドに対応するテーマ設計は、柔軟性と再利用性を高める重要な手法です。一元管理されたテーマとダイナミックな切り替え機能を実装することで、効率的な開発とメンテナンスが可能になります。最後に、この記事で学んだ内容を活かして、複雑なアプリケーションでも拡張性の高いテーマ設計を実現しましょう。

まとめ

本記事では、ReactアプリケーションでTypeScriptを活用し、型安全かつ拡張性のあるカスタムテーマを設計する方法を解説しました。テーマ設計の基本から始まり、コンテキストを用いたテーマ管理、styled-componentsやMaterial-UIを活用した実装方法、テーマの切り替え機能、複数ブランド向けの応用例までを網羅しました。

型安全なテーマ設計を導入することで、以下のメリットを得られます:

  • 開発効率の向上: 自動補完と型チェックによりエラーを未然に防ぐ。
  • 一貫性のあるデザイン: プロジェクト全体で統一された見た目を実現。
  • 拡張性と柔軟性: ダークモードやブランド切り替えなどの機能追加が容易。

これらの知識を活かし、あなたのアプリケーションに最適なテーマ設計を取り入れ、効率的で魅力的なUIを提供してください。テーマ設計は、ユーザー体験を向上させるだけでなく、開発者の生産性を高める重要な要素です。

コメント

コメントする

目次