Reactのグローバル状態と連携した再利用可能なコンポーネントの作成方法

Reactは、モジュール化された設計とコンポーネント指向のアプローチで、多くの開発者に支持されています。しかし、複雑なアプリケーションを構築する際には、状態管理とコンポーネントの再利用性が課題となることがあります。特に、アプリ全体で共有されるデータを効率的に管理する「グローバル状態」の設計は、機能の拡張性や保守性に直結します。一方、再利用可能なコンポーネントを作成することで、コードの冗長性を減らし、開発スピードを向上させることが可能です。本記事では、グローバル状態を効果的に活用しながら、再利用可能なコンポーネントを作成する具体例とベストプラクティスについて解説します。これにより、効率的で拡張性の高いReactアプリケーションを構築するための知識を得ることができます。

目次

グローバル状態とは?


グローバル状態とは、アプリケーション全体で共有されるデータを一元的に管理する仕組みを指します。Reactにおいては、個々のコンポーネントが独自の状態(ローカル状態)を持つことが一般的ですが、複数のコンポーネント間で状態を共有する必要がある場合、グローバル状態を利用することで効率的なデータ管理が可能になります。

グローバル状態のメリット

  1. 一貫性のあるデータ管理:アプリ全体で統一されたデータの管理が可能になり、状態の不整合を防ぎます。
  2. 再利用性の向上:複数のコンポーネントが同じデータを参照できるため、コードの重複を減らせます。
  3. 状態管理の単純化:データを一元管理することで、ローカル状態の増加による管理負担を軽減します。

グローバル状態の課題

  1. 複雑性の増加:状態の流れが見えにくくなることで、デバッグが難しくなる可能性があります。
  2. パフォーマンスの低下:不必要な再レンダリングが発生すると、アプリケーションのパフォーマンスに影響します。
  3. 過剰な依存:すべての状態をグローバルにすると、設計が硬直的になり柔軟性を失うことがあります。

Reactにおけるグローバル状態の管理方法

  1. Context API:React標準の機能で、小規模なアプリケーションやシンプルなデータ共有に適しています。
  2. Redux:複雑なアプリケーションでの状態管理に強力なライブラリで、一貫性とデバッグ性を向上させます。
  3. React QueryやZustand:非同期データの管理や簡単な状態管理に特化した選択肢です。

Reactでグローバル状態を適切に活用することは、アプリケーションの保守性やパフォーマンスを向上させる重要なポイントです。次章では、再利用可能なコンポーネントの特徴について詳しく見ていきます。

再利用可能なコンポーネントの特徴

Reactのコンポーネントは、小さな単位で機能を分割し、複数の場面で再利用できる設計が可能です。これにより、開発の効率を向上させ、コードのメンテナンスを容易にします。しかし、単にコンポーネントを作成するだけでは、再利用性を十分に確保することはできません。以下では、再利用可能なコンポーネントの特徴と設計のポイントについて説明します。

再利用可能なコンポーネントの主な特徴

  1. 単一の責任:コンポーネントは一つの明確な目的を持ち、それ以外の責任を持たないように設計します。
  2. 柔軟なプロパティ(props):propsを活用して、異なるデータや設定で動作をカスタマイズできるようにします。
  3. 依存の最小化:外部の状態やコンポーネントに強く依存しない設計にすることで、独立性を高めます。
  4. デザインの一貫性:UIのデザインガイドラインに基づいた統一されたスタイルを持つことが重要です。

再利用性を高めるための設計ポイント

  1. 汎用的なデータ構造:受け取るpropsを柔軟に設計し、異なるユースケースで使用できるようにします。
  2. コンポーネントの分割:大きなコンポーネントを小さな部品に分割し、それぞれが独立して機能するようにします。
  3. カスタマイズ性の提供:スタイルや機能をpropsやコンポジションで変更できるようにします。
  4. テスト容易性:シンプルな設計により、ユニットテストを簡単に実施できるようにします。

再利用可能なコンポーネントの具体例

以下は、再利用可能なボタンコンポーネントの例です:

import React from 'react';

const Button = ({ label, onClick, style, disabled }) => {
  return (
    <button 
      onClick={onClick} 
      style={{ padding: '10px 20px', ...style }} 
      disabled={disabled}
    >
      {label}
    </button>
  );
};

export default Button;

このボタンは、labelonClickなどのpropsを利用して簡単にカスタマイズ可能です。例えば、異なるラベルやスタイルを渡すことで複数の用途で使用できます。

再利用可能なコンポーネントのメリット

  1. 開発速度の向上:既存のコンポーネントを使い回すことで、新しい機能を素早く実装できます。
  2. 保守性の向上:コードが重複しないため、修正が容易になります。
  3. デザインの統一:UIの一貫性を保つことができます。

再利用可能なコンポーネントを設計することで、開発の効率と品質が大幅に向上します。次章では、グローバル状態とコンポーネントをどのように連携させるかを解説します。

グローバル状態とコンポーネントの連携

Reactでグローバル状態を利用する際、再利用可能なコンポーネントと組み合わせることで、効率的で柔軟なアプリケーション設計が可能になります。この章では、ReactのContext APIReduxを使用してグローバル状態を管理し、コンポーネントと連携させる方法を解説します。

グローバル状態管理の基本


グローバル状態は、アプリケーション全体のデータを一元管理し、必要なコンポーネントに共有するための仕組みです。以下のような状況で有効です:

  • ユーザー認証情報の管理
  • テーマ設定(ダークモードなど)の適用
  • アプリ全体のカート情報やフィルター条件の共有

グローバル状態の管理には、以下のツールが一般的に利用されます:

  1. Context API:軽量で小規模な状態管理に適しています。
  2. Redux:スケーラブルで複雑なアプリケーション向けの状態管理ライブラリです。

Context APIを使用した連携

Context APIを用いると、親コンポーネントから深い階層の子コンポーネントまでデータを効率よく渡すことができます。以下は、テーマを切り替えるための例です:

import React, { createContext, useContext, useState } from 'react';

// コンテキストの作成
const ThemeContext = createContext();

const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');
  const toggleTheme = () => setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));

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

// コンテキストを使用するカスタムフック
const useTheme = () => useContext(ThemeContext);

const ThemedButton = () => {
  const { theme, toggleTheme } = useTheme();
  return (
    <button 
      style={{ 
        backgroundColor: theme === 'light' ? '#fff' : '#333', 
        color: theme === 'light' ? '#000' : '#fff',
        padding: '10px 20px' 
      }} 
      onClick={toggleTheme}
    >
      {theme === 'light' ? 'Switch to Dark Mode' : 'Switch to Light Mode'}
    </button>
  );
};

const App = () => (
  <ThemeProvider>
    <ThemedButton />
  </ThemeProvider>
);

export default App;

この例では、ThemeContextを用いてテーマ情報をグローバルに管理し、ThemedButtonコンポーネントがその状態を使用しています。

Reduxを使用した連携

Reduxは、より大規模で複雑な状態管理に適しており、アクションやリデューサーを用いて状態を変更します。以下はカート情報を管理する例です:

import { createStore } from 'redux';
import { Provider, useSelector, useDispatch } from 'react-redux';

// 初期状態
const initialState = { cart: [] };

// リデューサーの定義
const cartReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD_ITEM':
      return { ...state, cart: [...state.cart, action.payload] };
    default:
      return state;
  }
};

// ストアの作成
const store = createStore(cartReducer);

const CartButton = () => {
  const dispatch = useDispatch();
  const addItem = () => dispatch({ type: 'ADD_ITEM', payload: { id: 1, name: 'Item 1' } });

  return <button onClick={addItem}>Add to Cart</button>;
};

const CartDisplay = () => {
  const cart = useSelector((state) => state.cart);

  return (
    <ul>
      {cart.map((item, index) => (
        <li key={index}>{item.name}</li>
      ))}
    </ul>
  );
};

const App = () => (
  <Provider store={store}>
    <CartButton />
    <CartDisplay />
  </Provider>
);

export default App;

この例では、Reduxを用いてカート情報をグローバルに管理し、CartButtonでアイテムを追加し、CartDisplayでカート内容を表示します。

グローバル状態とコンポーネント連携のポイント

  1. 適切なスコープの選択:必要なコンポーネントにのみグローバル状態を渡すことでパフォーマンスを最適化します。
  2. 状態の分割管理:複数の小さなコンテキストやリデューサーを活用することで管理しやすくします。
  3. パフォーマンスの最適化:React.memoやuseMemoを活用して、不要な再レンダリングを防ぎます。

次章では、具体的なサンプルコードを用いて、状態を持つ再利用可能なコンポーネントの作成を解説します。

サンプル実装:状態を持つボタンコンポーネント

グローバル状態と連携する再利用可能なコンポーネントの作成を具体例で解説します。この章では、クリック時に状態が変化するボタンコンポーネントを設計し、Context APIを活用してグローバル状態と連携させる方法を示します。

実装の目的


クリック時にボタンの状態が変化し、その状態を他のコンポーネントでも利用できるようにします。例えば、「お気に入り」ボタンを押すと状態が切り替わり、他のコンポーネントでお気に入りリストとして表示されます。

全体の構成

  1. グローバル状態を管理するFavoritesContextを作成
  2. 再利用可能なFavoriteButtonコンポーネントを作成
  3. 状態を表示するコンポーネントを作成

コードの詳細

まず、Contextを使ったグローバル状態の管理を行います。

import React, { createContext, useContext, useState } from 'react';

// FavoritesContextの作成
const FavoritesContext = createContext();

// コンテキストプロバイダー
const FavoritesProvider = ({ children }) => {
  const [favorites, setFavorites] = useState([]);

  const toggleFavorite = (id) => {
    setFavorites((prevFavorites) =>
      prevFavorites.includes(id)
        ? prevFavorites.filter((favId) => favId !== id)
        : [...prevFavorites, id]
    );
  };

  return (
    <FavoritesContext.Provider value={{ favorites, toggleFavorite }}>
      {children}
    </FavoritesContext.Provider>
  );
};

// コンテキストを利用するカスタムフック
const useFavorites = () => useContext(FavoritesContext);

export { FavoritesProvider, useFavorites };

次に、FavoriteButtonコンポーネントを実装します。

import React from 'react';
import { useFavorites } from './FavoritesContext';

const FavoriteButton = ({ itemId }) => {
  const { favorites, toggleFavorite } = useFavorites();

  const isFavorite = favorites.includes(itemId);

  return (
    <button
      onClick={() => toggleFavorite(itemId)}
      style={{
        padding: '10px 20px',
        backgroundColor: isFavorite ? 'gold' : 'gray',
        color: 'white',
        border: 'none',
        cursor: 'pointer',
      }}
    >
      {isFavorite ? 'Unfavorite' : 'Favorite'}
    </button>
  );
};

export default FavoriteButton;

最後に、状態を表示するコンポーネントを作成します。

import React from 'react';
import { useFavorites } from './FavoritesContext';

const FavoritesList = () => {
  const { favorites } = useFavorites();

  return (
    <div>
      <h3>Favorite Items:</h3>
      <ul>
        {favorites.map((id) => (
          <li key={id}>Item {id}</li>
        ))}
      </ul>
    </div>
  );
};

export default FavoritesList;

これらを統合してアプリケーションを完成させます。

import React from 'react';
import { FavoritesProvider } from './FavoritesContext';
import FavoriteButton from './FavoriteButton';
import FavoritesList from './FavoritesList';

const App = () => {
  return (
    <FavoritesProvider>
      <h1>Favorite Button Example</h1>
      <FavoriteButton itemId={1} />
      <FavoriteButton itemId={2} />
      <FavoriteButton itemId={3} />
      <FavoritesList />
    </FavoritesProvider>
  );
};

export default App;

実行結果

  • ボタンをクリックすると、色が切り替わり「Favorite」または「Unfavorite」と表示されます。
  • 「Favorites List」に選択されたアイテムのリストがリアルタイムで反映されます。

再利用性のポイント

  1. データの動的変更:ボタンのitemIdをpropsで受け取ることで、任意のアイテムに対応できます。
  2. カスタマイズ可能なスタイルstyleプロパティや追加のpropsを用意することで柔軟性を向上させます。
  3. 分離されたロジック:状態管理をFavoritesContextに委譲することで、ボタン自体の設計がシンプルになります。

次章では、もう少し複雑なテーマ切り替えコンポーネントを実装していきます。

サンプル実装:テーマ切り替えコンポーネント

テーマ切り替え機能は、Reactアプリケーションで一般的な機能の一つです。この章では、Context APIを利用してグローバル状態としてテーマを管理し、再利用可能なテーマ切り替えコンポーネントを実装する方法を解説します。

実装の目的

  • ユーザーがボタンをクリックすることで、テーマ(ライトモード/ダークモード)を切り替える機能を提供します。
  • アプリ全体で選択されたテーマが適用される設計とします。

全体の構成

  1. グローバル状態でテーマを管理するThemeContextの作成
  2. 再利用可能なThemeToggleButtonコンポーネントを作成
  3. グローバルテーマを適用するためのスタイル設計

コードの詳細

まず、テーマを管理するためのContextを作成します。

import React, { createContext, useContext, useState } from 'react';

// ThemeContextの作成
const ThemeContext = createContext();

// コンテキストプロバイダー
const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
  };

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

// コンテキストを利用するカスタムフック
const useTheme = () => useContext(ThemeContext);

export { ThemeProvider, useTheme };

次に、テーマを切り替えるためのThemeToggleButtonコンポーネントを作成します。

import React from 'react';
import { useTheme } from './ThemeContext';

const ThemeToggleButton = () => {
  const { theme, toggleTheme } = useTheme();

  return (
    <button
      onClick={toggleTheme}
      style={{
        padding: '10px 20px',
        backgroundColor: theme === 'light' ? '#000' : '#fff',
        color: theme === 'light' ? '#fff' : '#000',
        border: 'none',
        cursor: 'pointer',
      }}
    >
      Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
    </button>
  );
};

export default ThemeToggleButton;

次に、アプリ全体にテーマが適用されるように、スタイルを設定します。テーマに応じて背景色を動的に変更します。

import React from 'react';
import { useTheme } from './ThemeContext';

const ThemedContainer = ({ children }) => {
  const { theme } = useTheme();

  return (
    <div
      style={{
        backgroundColor: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#000' : '#fff',
        minHeight: '100vh',
        padding: '20px',
        transition: 'background-color 0.3s, color 0.3s',
      }}
    >
      {children}
    </div>
  );
};

export default ThemedContainer;

最後に、全てを統合してアプリケーションを構築します。

import React from 'react';
import { ThemeProvider } from './ThemeContext';
import ThemeToggleButton from './ThemeToggleButton';
import ThemedContainer from './ThemedContainer';

const App = () => {
  return (
    <ThemeProvider>
      <ThemedContainer>
        <h1>Theme Toggle Example</h1>
        <ThemeToggleButton />
      </ThemedContainer>
    </ThemeProvider>
  );
};

export default App;

実行結果

  • ページ全体の背景色と文字色が、ボタンのクリックに応じて切り替わります。
  • ボタンのテキストは、現在のテーマに応じて「Dark Mode」または「Light Mode」を表示します。

再利用性のポイント

  1. グローバルテーマ管理:テーマの状態と切り替えロジックをThemeContextに分離し、どのコンポーネントからも利用可能にしています。
  2. 独立したスタイリングロジックThemedContainerを活用することで、テーマの変更がアプリ全体に影響を及ぼします。
  3. 動的なカスタマイズ:スタイルやテーマの追加を容易に行える設計です。

次章では、再利用可能なコンポーネントのテスト戦略について解説します。

再利用コンポーネントのテスト戦略

再利用可能なコンポーネントを作成する際、機能の正確性や動作の安定性を保証するために、テストを実施することは不可欠です。この章では、再利用コンポーネントのテストを効率的に行うための戦略や具体的な手法について解説します。

テスト戦略の基本


再利用コンポーネントのテストには以下の3つのレベルが重要です:

  1. ユニットテスト:個々のコンポーネントが意図したとおりに動作することを検証します。
  2. スナップショットテスト:コンポーネントのレンダリング結果が変化していないことを確認します。
  3. 統合テスト:コンポーネントが他の部分と連携して正しく機能するかを検証します。

ユニットテストの実施

ユニットテストでは、コンポーネントのロジックやUIが期待通りに動作するかを検証します。
以下は、ThemeToggleButtonコンポーネントのユニットテストの例です:

import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { ThemeProvider } from './ThemeContext';
import ThemeToggleButton from './ThemeToggleButton';

test('ThemeToggleButton toggles theme on click', () => {
  render(
    <ThemeProvider>
      <ThemeToggleButton />
    </ThemeProvider>
  );

  const button = screen.getByText(/Switch to Dark Mode/i);
  expect(button).toBeInTheDocument();

  // 初期状態はライトモード
  expect(button).toHaveStyle('background-color: black');

  // ボタンをクリックしてダークモードに切り替え
  fireEvent.click(button);
  expect(button).toHaveTextContent('Switch to Light Mode');
  expect(button).toHaveStyle('background-color: white');
});

このテストでは以下を確認しています:

  • 初期状態がライトモードであること
  • ボタンをクリックすると、テーマがダークモードに切り替わること

スナップショットテストの実施

スナップショットテストでは、コンポーネントのレンダリング結果が意図せず変更されていないかを検証します。

import React from 'react';
import renderer from 'react-test-renderer';
import { ThemeProvider } from './ThemeContext';
import ThemeToggleButton from './ThemeToggleButton';

test('ThemeToggleButton renders correctly', () => {
  const tree = renderer
    .create(
      <ThemeProvider>
        <ThemeToggleButton />
      </ThemeProvider>
    )
    .toJSON();
  expect(tree).toMatchSnapshot();
});

このテストでは、ThemeToggleButtonが適切にレンダリングされているかを検証し、意図しないUIの変更を防ぎます。

統合テストの実施

統合テストでは、複数のコンポーネントが連携して正しく動作することを確認します。

以下は、ThemedContainerThemeToggleButtonの統合テストの例です:

import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { ThemeProvider } from './ThemeContext';
import ThemedContainer from './ThemedContainer';
import ThemeToggleButton from './ThemeToggleButton';

test('ThemedContainer updates background on theme toggle', () => {
  render(
    <ThemeProvider>
      <ThemedContainer>
        <ThemeToggleButton />
      </ThemedContainer>
    </ThemeProvider>
  );

  const container = screen.getByText(/Switch to Dark Mode/i).parentElement;

  // 初期状態はライトテーマ
  expect(container).toHaveStyle('background-color: white');

  // ボタンをクリックしてテーマを変更
  const button = screen.getByText(/Switch to Dark Mode/i);
  fireEvent.click(button);

  // ダークテーマが適用されることを確認
  expect(container).toHaveStyle('background-color: rgb(51, 51, 51)');
});

このテストでは、ThemeToggleButtonの操作に応じて、ThemedContainerの背景色が正しく切り替わるかを確認しています。

ベストプラクティス

  1. 小さな単位でテストを実施:コンポーネント単位でテストを行い、問題の特定を容易にします。
  2. カバレッジの確認:テストカバレッジを意識して、あらゆるケースを網羅するよう心がけます。
  3. 再利用可能なテストツールrenderfireEventをカスタムユーティリティとして抽象化し、他のテストでも再利用できるようにします。

次章では、コードの保守性とパフォーマンスの最適化方法について詳しく解説します。

コードの保守性とパフォーマンスの最適化

再利用可能なコンポーネントは、保守性とパフォーマンスの両方を意識して設計する必要があります。保守性が高ければ長期的なコード管理が容易になり、パフォーマンスが最適化されていればユーザーエクスペリエンスが向上します。この章では、それぞれの側面について具体的な方法を解説します。

コードの保守性を向上させる方法

  1. 単一責任の原則を遵守する
    コンポーネントは1つの明確な役割を持つべきです。例えば、状態を管理するコンポーネントとUIをレンダリングするコンポーネントを分けると、変更の影響範囲を小さく抑えられます。
   // 状態管理
   const useCounter = () => {
     const [count, setCount] = React.useState(0);
     const increment = () => setCount((prev) => prev + 1);
     return { count, increment };
   };

   // UI表示
   const Counter = ({ count, increment }) => (
     <button onClick={increment}>Count: {count}</button>
   );
  1. コンポーネントの再利用性を高める設計
    汎用的なpropsを設計し、特定のユースケースに依存しないようにします。たとえば、ボタンのラベルやスタイルを変更可能にするpropsを用意します。
   const Button = ({ label, onClick, style }) => (
     <button onClick={onClick} style={style}>
       {label}
     </button>
   );
  1. ドキュメントの整備
    propsや使用方法を明確に説明したコメントやREADMEを用意することで、他の開発者がコンポーネントを容易に理解できるようにします。
  2. テストの充実
    前章で説明したテスト戦略を活用して、コードの変更が意図しないバグを引き起こさないようにします。

パフォーマンスの最適化

  1. React.memoを活用する
    コンポーネントが同じpropsで再レンダリングされるのを防ぎます。たとえば、変更がない場合に再描画しないボタンコンポーネントの例です。
   import React from 'react';

   const Button = React.memo(({ onClick, label }) => {
     console.log('Rendered Button');
     return <button onClick={onClick}>{label}</button>;
   });
  1. useMemoとuseCallbackの利用
    重い計算やコールバック関数の再生成を防ぎ、不要なレンダリングを回避します。
   const MyComponent = ({ data }) => {
     const sortedData = React.useMemo(() => {
       console.log('Sorting data');
       return data.sort();
     }, [data]);

     const handleClick = React.useCallback(() => {
       console.log('Button clicked');
     }, []);

     return (
       <div>
         {sortedData.map((item, index) => (
           <div key={index}>{item}</div>
         ))}
         <button onClick={handleClick}>Click Me</button>
       </div>
     );
   };
  1. コンポーネントの分割と遅延ロード
    ReactのReact.lazyを用いることで、必要なコンポーネントのみを遅延ロードできます。
   import React, { Suspense } from 'react';

   const LazyComponent = React.lazy(() => import('./MyComponent'));

   const App = () => (
     <Suspense fallback={<div>Loading...</div>}>
       <LazyComponent />
     </Suspense>
   );
  1. デバウンスやスロットリング
    ユーザー入力やスクロールイベントなどの頻繁な更新を抑制し、パフォーマンスを向上させます。
   const debounce = (func, delay) => {
     let timeout;
     return (...args) => {
       clearTimeout(timeout);
       timeout = setTimeout(() => func(...args), delay);
     };
   };

   const handleResize = debounce(() => {
     console.log('Resized');
   }, 300);

   window.addEventListener('resize', handleResize);

コードの保守性とパフォーマンスの両立

  1. 明確なコンポーネント設計
    再利用性を意識した汎用コンポーネントの設計により、保守性とパフォーマンスのバランスを取ります。
  2. パフォーマンスモニタリングの導入
    開発段階でReact DevToolsを活用してパフォーマンスを計測し、ボトルネックを特定します。
  3. 継続的なリファクタリング
    コードが複雑化しないように定期的にリファクタリングを行い、冗長なロジックを削除します。

これらのアプローチを活用することで、保守性とパフォーマンスの高いReactコンポーネントを実現できます。次章では、応用例としてダッシュボード用ウィジェットの設計を解説します。

応用例:ダッシュボード用ウィジェットの設計

ダッシュボードは、データを視覚化し、リアルタイムで更新可能なコンポーネントを複数組み合わせることが求められます。この章では、Reactを用いてグローバル状態を活用し、再利用可能なウィジェットコンポーネントを設計する方法を解説します。

実装の目的

  • データを動的に取得し、リアルタイムで表示するダッシュボードウィジェットを作成します。
  • ウィジェットは柔軟にカスタマイズ可能で、複数の用途に再利用できる設計にします。

全体の構成

  1. グローバル状態でデータを管理するDataContextの作成
  2. 再利用可能なWidgetコンポーネントを作成
  3. 複数のウィジェットを統合するダッシュボードレイアウトを作成

コードの詳細

まず、データを管理するContextを作成します。

import React, { createContext, useContext, useState, useEffect } from 'react';

// DataContextの作成
const DataContext = createContext();

// データプロバイダー
const DataProvider = ({ children }) => {
  const [data, setData] = useState({});

  useEffect(() => {
    // サンプルデータを5秒ごとに更新
    const interval = setInterval(() => {
      setData({
        temperature: Math.floor(Math.random() * 100),
        humidity: Math.floor(Math.random() * 100),
      });
    }, 5000);
    return () => clearInterval(interval);
  }, []);

  return <DataContext.Provider value={data}>{children}</DataContext.Provider>;
};

// データを利用するカスタムフック
const useData = () => useContext(DataContext);

export { DataProvider, useData };

次に、再利用可能なWidgetコンポーネントを作成します。

import React from 'react';

const Widget = ({ title, value, unit, style }) => (
  <div
    style={{
      border: '1px solid #ccc',
      borderRadius: '8px',
      padding: '16px',
      textAlign: 'center',
      ...style,
    }}
  >
    <h3>{title}</h3>
    <p style={{ fontSize: '24px', margin: '8px 0' }}>
      {value} {unit}
    </p>
  </div>
);

export default Widget;

続いて、Widgetを利用してダッシュボードを構築します。

import React from 'react';
import { useData } from './DataContext';
import Widget from './Widget';

const Dashboard = () => {
  const { temperature, humidity } = useData();

  return (
    <div
      style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
        gap: '16px',
        padding: '16px',
      }}
    >
      <Widget title="Temperature" value={temperature} unit="°C" />
      <Widget title="Humidity" value={humidity} unit="%" />
    </div>
  );
};

export default Dashboard;

最後に、全体を統合します。

import React from 'react';
import { DataProvider } from './DataContext';
import Dashboard from './Dashboard';

const App = () => (
  <DataProvider>
    <h1 style={{ textAlign: 'center', margin: '16px 0' }}>Dashboard</h1>
    <Dashboard />
  </DataProvider>
);

export default App;

実行結果

  • ダッシュボードには、温度と湿度のウィジェットが表示され、5秒ごとにデータが更新されます。
  • 新しいウィジェットを追加する際も、Widgetコンポーネントを利用して簡単に実現可能です。

応用ポイント

  1. 柔軟なレイアウト設計gridTemplateColumnsを動的に調整することで、レスポンシブなデザインを実現しています。
  2. 動的データ管理:グローバル状態としてデータを管理し、どのウィジェットからも利用可能にしています。
  3. カスタマイズ可能なスタイルWidgetstyleプロパティを使用して、ウィジェットごとにデザインを変更可能です。

拡張案

  1. APIからデータを取得する機能を追加し、リアルタイムの外部データに対応。
  2. ウィジェットのドラッグ&ドロップによるレイアウト変更を実装。
  3. グラフやチャートを含む高度な可視化コンポーネントの導入。

次章では、この記事全体をまとめ、学んだ内容の応用方法を簡潔に解説します。

まとめ

本記事では、Reactにおけるグローバル状態を活用した再利用可能なコンポーネントの設計方法を解説しました。グローバル状態の基礎から始まり、Context APIやReduxを使用した管理方法、再利用性を高めるコンポーネント設計のポイントを紹介しました。また、実際の応用例として、ダッシュボードウィジェットの構築を例に、柔軟で拡張可能なコンポーネント設計を実践的に示しました。

以下が本記事の要点です:

  • グローバル状態を利用してデータの一貫性を保ちながら効率的なアプリケーション設計を実現する。
  • 再利用可能なコンポーネントを設計することで、開発速度と保守性を向上させる。
  • ユニットテストやスナップショットテストを活用して、信頼性の高いコンポーネントを作成する。
  • ダッシュボードウィジェットのような実用的な応用例を通じて、学んだ内容を活用できるようにする。

これらをマスターすることで、Reactアプリケーションの効率と品質を大幅に向上させることができます。次のプロジェクトでぜひ活用してみてください。

コメント

コメントする

目次