ReactのuseMediaQueryフックを使ったコンポーネント切り替えの具体的な実装方法

Web開発において、画面サイズやデバイスの特性に応じてUIを最適化することは、ユーザーエクスペリエンスの向上に欠かせません。Reactでは、useMediaQueryフックを利用することで、画面幅や高さに応じたコンポーネントの表示やレイアウトを簡単に制御できます。本記事では、useMediaQueryフックの基本的な使い方から、実践的な応用例までを詳しく解説し、レスポンシブデザインを効率的に実現する方法をご紹介します。

目次

useMediaQueryとは

useMediaQueryは、Reactアプリケーションで画面サイズやデバイスの特性を検知するために使用されるフックです。このフックを使用することで、CSSのメディアクエリのような機能をJavaScriptで直接扱うことができます。これにより、特定の条件に応じたコンポーネントの表示や非表示、レイアウト変更などを柔軟に実現可能です。

基本的な仕組み

useMediaQueryは、指定したメディアクエリ(例: '(min-width: 600px)')を満たしているかどうかをチェックし、その結果を真偽値で返します。これを利用することで、条件に応じたUI変更が簡単に行えます。

使用例

例えば、画面幅が600px以上の場合に特定のコンポーネントを表示したい場合、useMediaQueryを使って次のように実装します。

import { useMediaQuery } from '@mui/material';

function ExampleComponent() {
  const isLargeScreen = useMediaQuery('(min-width:600px)');

  return (
    <div>
      {isLargeScreen ? <LargeComponent /> : <SmallComponent />}
    </div>
  );
}

このように、useMediaQueryを活用することで、デバイスや画面サイズに応じた動的なコンポーネントの制御が可能になります。

useMediaQueryをインストールする方法

ReactでuseMediaQueryフックを使用するには、一般的にMaterial-UI(MUI)ライブラリを利用します。以下に、インストール手順と設定方法を解説します。

必要なライブラリをインストールする

useMediaQueryはMUIの一部として提供されています。以下のコマンドを使用してMUIのコアライブラリをインストールします。

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

このコマンドで、MUIのコンポーネントやスタイリング機能を利用するための依存関係がインストールされます。

インストール確認

インストール後、以下のコマンドを実行して、useMediaQueryが含まれるMUIパッケージが正しくインストールされていることを確認します。

npm list @mui/material

基本的なセットアップ

インストールが完了したら、ReactコンポーネントでuseMediaQueryをインポートして利用できるようになります。

import { useMediaQuery } from '@mui/material';

Material-UIのテーマを利用する場合

MUIのテーマと組み合わせてuseMediaQueryを使用する場合、以下のようにテーマを提供する必要があります。

import { ThemeProvider, createTheme } from '@mui/material/styles';
import { useMediaQuery } from '@mui/material';

const theme = createTheme();

function App() {
  const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));

  return (
    <ThemeProvider theme={theme}>
      <div>
        {isSmallScreen ? 'Small Screen' : 'Large Screen'}
      </div>
    </ThemeProvider>
  );
}

これで、useMediaQueryの準備は完了です。次は、具体的な実装方法を見ていきましょう。

useMediaQueryの基本的な使い方

useMediaQueryフックは、特定の画面サイズや条件に応じてコンポーネントの表示や動作を切り替えるための便利なツールです。ここでは、基本的な使用方法をシンプルなコード例とともに解説します。

画面幅に応じた表示切り替え

以下の例では、画面幅が600px以上の場合とそれ未満の場合で表示するテキストを切り替えます。

import React from 'react';
import { useMediaQuery } from '@mui/material';

function ResponsiveText() {
  const isLargeScreen = useMediaQuery('(min-width:600px)');

  return (
    <div>
      {isLargeScreen ? (
        <p>この画面は600px以上の大きさです。</p>
      ) : (
        <p>この画面は600px未満の大きさです。</p>
      )}
    </div>
  );
}

export default ResponsiveText;

このコードでは、isLargeScreenが真の場合に「600px以上の大きさ」、偽の場合に「600px未満の大きさ」が表示されます。

複数のメディアクエリを使用する

複数の条件を扱いたい場合も簡単です。次の例では、画面幅がタブレットサイズかどうかを判定します。

function MultiQueryExample() {
  const isTablet = useMediaQuery('(min-width:600px) and (max-width:900px)');

  return (
    <div>
      {isTablet ? (
        <p>タブレットサイズの画面です。</p>
      ) : (
        <p>タブレットサイズ以外の画面です。</p>
      )}
    </div>
  );
}

テーマブレークポイントを利用する

MUIのテーマを使用してブレークポイントを管理する場合、以下のように記述します。

import { useTheme } from '@mui/material/styles';
import { useMediaQuery } from '@mui/material';

function ThemeQueryExample() {
  const theme = useTheme();
  const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));

  return (
    <div>
      {isSmallScreen ? (
        <p>小さい画面(スマートフォン)です。</p>
      ) : (
        <p>大きい画面です。</p>
      )}
    </div>
  );
}

注意点

  • メディアクエリの条件はシンプルに保つことが推奨されます。
  • 必要に応じて、MUIのテーマを活用することでメンテナンス性が向上します。

useMediaQueryの基本を押さえることで、画面サイズに応じた柔軟なレスポンシブデザインを実現できます。次は、ブレークポイントの設定方法を詳しく見ていきましょう。

ブレークポイントを定義する方法

レスポンシブデザインを実現するために、画面サイズに応じた条件を設定する「ブレークポイント」を定義することが重要です。Reactでは、Material-UIのテーマ機能を使うことで、ブレークポイントを効率的に管理できます。

Material-UIのテーマでブレークポイントを設定する

Material-UI(MUI)では、あらかじめ以下の標準ブレークポイントが定義されています。

  • xs(0px以上)
  • sm(600px以上)
  • md(900px以上)
  • lg(1200px以上)
  • xl(1536px以上)

これらは、テーマオブジェクトを通じて簡単に利用できます。

例: 標準ブレークポイントを使用する

import { useTheme } from '@mui/material/styles';
import { useMediaQuery } from '@mui/material';

function StandardBreakpoints() {
  const theme = useTheme();
  const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));

  return (
    <div>
      {isSmallScreen ? (
        <p>小さい画面(600px以下)です。</p>
      ) : (
        <p>大きい画面(600px以上)です。</p>
      )}
    </div>
  );
}

カスタムブレークポイントの定義

必要に応じて、独自のブレークポイントを定義することも可能です。以下のコードは、createThemeを使ってカスタムブレークポイントを設定する例です。

import { createTheme, ThemeProvider, useTheme } from '@mui/material/styles';
import { useMediaQuery } from '@mui/material';

const customTheme = createTheme({
  breakpoints: {
    values: {
      xs: 0,
      sm: 480,
      md: 768,
      lg: 1024,
      xl: 1440,
    },
  },
});

function CustomBreakpoints() {
  const theme = useTheme();
  const isTablet = useMediaQuery(theme.breakpoints.between('sm', 'md'));

  return (
    <div>
      {isTablet ? (
        <p>タブレットサイズの画面です(480pxから768px)。</p>
      ) : (
        <p>タブレットサイズ以外の画面です。</p>
      )}
    </div>
  );
}

function App() {
  return (
    <ThemeProvider theme={customTheme}>
      <CustomBreakpoints />
    </ThemeProvider>
  );
}

export default App;

ブレークポイントの使用例

  1. 小さい画面向けのコンポーネント表示
    theme.breakpoints.down('sm')を使用して、モバイル向けのレイアウトを作成します。
  2. 特定の範囲で表示を切り替える
    theme.breakpoints.between('sm', 'md')を使用して、特定の範囲の画面サイズに対応します。
  3. 大きい画面向けのコンポーネント表示
    theme.breakpoints.up('lg')を使用して、デスクトップ向けのレイアウトを作成します。

注意点

  • ブレークポイントの設定は一貫性を保つことが重要です。
  • 必要に応じて、カスタムブレークポイントを使い、プロジェクトに最適なレスポンシブ設計を実現してください。

これらの方法を使用することで、画面サイズに応じた柔軟なデザインが可能になります。次は、実践的なコンポーネント例を見ていきます。

実践例:ヘッダーのレイアウト切り替え

レスポンシブデザインでは、画面サイズに応じてヘッダーのレイアウトを切り替えることが一般的です。このセクションでは、useMediaQueryフックを使って、画面サイズに応じたヘッダーのレイアウトを動的に変更する実践例を紹介します。

要件

  • 大画面(600px以上): ナビゲーションメニューを横並びで表示。
  • 小画面(600px未満): ハンバーガーメニューに切り替え。

コード例

以下のコードは、useMediaQueryを使用して、画面サイズに応じてヘッダーのレイアウトを切り替える例です。

import React from 'react';
import { useMediaQuery } from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu';

function ResponsiveHeader() {
  const isLargeScreen = useMediaQuery('(min-width:600px)');

  return (
    <header style={styles.header}>
      <h1 style={styles.logo}>MyApp</h1>
      {isLargeScreen ? (
        <nav style={styles.nav}>
          <a href="#home" style={styles.navItem}>Home</a>
          <a href="#about" style={styles.navItem}>About</a>
          <a href="#contact" style={styles.navItem}>Contact</a>
        </nav>
      ) : (
        <MenuIcon style={styles.menuIcon} />
      )}
    </header>
  );
}

const styles = {
  header: {
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: '10px 20px',
    backgroundColor: '#333',
    color: '#fff',
  },
  logo: {
    fontSize: '1.5em',
  },
  nav: {
    display: 'flex',
    gap: '15px',
  },
  navItem: {
    color: '#fff',
    textDecoration: 'none',
    fontSize: '1em',
  },
  menuIcon: {
    fontSize: '2em',
    cursor: 'pointer',
  },
};

export default ResponsiveHeader;

動作説明

  1. 大画面モード
  • isLargeScreentrueの場合、<nav>タグを使用してナビゲーションリンクを横並びで表示します。
  1. 小画面モード
  • isLargeScreenfalseの場合、<MenuIcon>(ハンバーガーメニューアイコン)を表示します。

応用: ハンバーガーメニューの展開

さらに、小画面モードでハンバーガーメニューをクリックすると、サイドバーが表示されるようにすることも可能です。以下の例はその拡張機能です。

import React, { useState } from 'react';
import { useMediaQuery } from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu';

function ResponsiveHeaderWithMenu() {
  const isLargeScreen = useMediaQuery('(min-width:600px)');
  const [menuOpen, setMenuOpen] = useState(false);

  const toggleMenu = () => setMenuOpen(!menuOpen);

  return (
    <header style={styles.header}>
      <h1 style={styles.logo}>MyApp</h1>
      {isLargeScreen ? (
        <nav style={styles.nav}>
          <a href="#home" style={styles.navItem}>Home</a>
          <a href="#about" style={styles.navItem}>About</a>
          <a href="#contact" style={styles.navItem}>Contact</a>
        </nav>
      ) : (
        <div>
          <MenuIcon style={styles.menuIcon} onClick={toggleMenu} />
          {menuOpen && (
            <div style={styles.sidebar}>
              <a href="#home" style={styles.navItem}>Home</a>
              <a href="#about" style={styles.navItem}>About</a>
              <a href="#contact" style={styles.navItem}>Contact</a>
            </div>
          )}
        </div>
      )}
    </header>
  );
}

const styles = {
  ...styles, // 前述のstylesを流用
  sidebar: {
    position: 'absolute',
    top: '50px',
    left: '0',
    backgroundColor: '#333',
    color: '#fff',
    width: '200px',
    padding: '10px',
    display: 'flex',
    flexDirection: 'column',
    gap: '10px',
  },
};

export default ResponsiveHeaderWithMenu;

注意点

  • 小画面用のメニューはユーザビリティを考慮して適切な位置に配置してください。
  • 追加機能を実装する際は、ステート管理を適切に行い、動作の一貫性を確保することが重要です。

これにより、画面サイズに応じた柔軟で直感的なヘッダーのレイアウトを実現できます。次は、カードコンポーネントのグリッド切り替えについて解説します。

実践例:カードコンポーネントのグリッド切り替え

画面サイズに応じてカードコンポーネントのレイアウトを変更することは、レスポンシブデザインにおいてよく求められる要件です。ここでは、useMediaQueryを活用して、カードのグリッドレイアウトを切り替える具体的な実装例を紹介します。

要件

  • 大画面(900px以上): カードを3列表示。
  • 中画面(600px以上900px未満): カードを2列表示。
  • 小画面(600px未満): カードを1列表示。

コード例

以下のコードでは、useMediaQueryを使用して、画面幅に応じた列数を動的に設定します。

import React from 'react';
import { useMediaQuery } from '@mui/material';

function ResponsiveCardGrid() {
  const isLargeScreen = useMediaQuery('(min-width:900px)');
  const isMediumScreen = useMediaQuery('(min-width:600px) and (max-width:899px)');

  const getGridColumns = () => {
    if (isLargeScreen) return 3;
    if (isMediumScreen) return 2;
    return 1;
  };

  const gridStyle = {
    display: 'grid',
    gridTemplateColumns: `repeat(${getGridColumns()}, 1fr)`,
    gap: '20px',
    padding: '20px',
  };

  return (
    <div style={gridStyle}>
      {[...Array(9)].map((_, index) => (
        <div key={index} style={styles.card}>
          <h3>カード {index + 1}</h3>
          <p>これはレスポンシブカードです。</p>
        </div>
      ))}
    </div>
  );
}

const styles = {
  card: {
    backgroundColor: '#f0f0f0',
    border: '1px solid #ddd',
    borderRadius: '8px',
    padding: '20px',
    textAlign: 'center',
  },
};

export default ResponsiveCardGrid;

動作説明

  1. 列数の計算
  • isLargeScreentrueの場合、3列。
  • isMediumScreentrueの場合、2列。
  • 上記以外では1列に設定します。
  1. CSSグリッドの使用
  • gridTemplateColumnsプロパティを使い、列数を動的に切り替えます。
  1. カードの描画
  • Arrayを使ってカードを繰り返し生成し、グリッド内に配置します。

カスタマイズ: カードデザインの拡張

カードコンポーネントのデザインを拡張して、画像やボタンを追加する例を示します。

const styles = {
  ...styles,
  card: {
    ...styles.card,
    boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
  },
};

さらに、画像やボタンを含むカードの内容を追加できます。

<div key={index} style={styles.card}>
  <img src={`https://via.placeholder.com/150?text=Card+${index + 1}`} alt={`Card ${index + 1}`} style={{ width: '100%', borderRadius: '8px' }} />
  <h3>カード {index + 1}</h3>
  <p>これはレスポンシブカードです。</p>
  <button style={{ padding: '10px 15px', border: 'none', backgroundColor: '#007bff', color: '#fff', borderRadius: '5px' }}>詳細を見る</button>
</div>

応用例

  • 商品リストの表示: ECサイトの製品ギャラリーとして利用可能です。
  • 画像ギャラリーの構築: 画像のグリッドレイアウトに応用できます。

注意点

  • コンポーネントの可読性を保つため、スタイルは外部CSSまたはスタイルライブラリに分離することを検討してください。
  • 高密度なデータを扱う場合は、パフォーマンスに配慮し、仮想スクロールを検討してください。

このアプローチを使用すると、どの画面サイズでも美しいカードレイアウトを実現できます。次は、useMediaQueryのデバッグとトラブルシューティングについて解説します。

useMediaQueryのデバッグとトラブルシューティング

useMediaQueryを使用する際、期待通りに動作しない場合があります。その原因と解決方法を理解することで、問題を効率的に解決できます。このセクションでは、よくある問題とその対処法を解説します。

よくある問題

1. メディアクエリの条件が無効

  • 問題: メディアクエリの条件が間違っているため、フックが正しい値を返さない。
  • : '(min-width600px)'のように、構文エラーがある場合。
  • 解決方法: メディアクエリの条件を正しい構文で記述します。例えば:
  const isLargeScreen = useMediaQuery('(min-width:600px)');

2. ブラウザ互換性の問題

  • 問題: 特定のブラウザでメディアクエリが正しく動作しない。
  • 解決方法: window.matchMediaの互換性を確認します。matchMediaがサポートされていない場合は、ポリフィルを使用します。

3. サーバーサイドレンダリング(SSR)での問題

  • 問題: SSR環境(Next.jsなど)でuseMediaQueryがクライアントとサーバーで異なる値を返す。
  • 解決方法:
  • デフォルト値を設定して、初期のレンダリングで問題を回避します。
    jsx const isLargeScreen = useMediaQuery('(min-width:600px)', { noSsr: true });
  • または、useEffectを使用してクライアントでのみメディアクエリを実行します。

4. テーマブレークポイントが適用されない

  • 問題: Material-UIのテーマで定義したブレークポイントが適用されていない。
  • 解決方法:
  • 必ずThemeProviderを正しく設定してください。
  import { ThemeProvider, createTheme } from '@mui/material/styles';
  const theme = createTheme();
  <ThemeProvider theme={theme}>
    {/* コンポーネント */}
  </ThemeProvider>

トラブルシューティングの方法

1. メディアクエリの動作確認

  • ブラウザのデベロッパーツールを使用して、指定したメディアクエリが正しくマッチしているかを確認します。
  • コンソールログを使って値をデバッグします。
  console.log('isLargeScreen:', isLargeScreen);

2. スタイルの競合を確認

  • メディアクエリの条件が正しい場合でも、CSSやテーマ設定の競合が原因で意図しない動作になることがあります。スタイルの優先順位を確認しましょう。

3. 必要に応じてデフォルトのスタイルを設定

  • useMediaQueryが期待通りの値を返さない場合、フォールバックのスタイルを指定します。

実践例: デバッグ用コンポーネント

以下のコンポーネントは、useMediaQueryの動作を簡単に確認できるデバッグツールです。

function DebugMediaQuery() {
  const isLargeScreen = useMediaQuery('(min-width:900px)');
  const isMediumScreen = useMediaQuery('(min-width:600px) and (max-width:899px)');
  const isSmallScreen = useMediaQuery('(max-width:599px)');

  console.log({ isLargeScreen, isMediumScreen, isSmallScreen });

  return (
    <div>
      <p>Large Screen: {isLargeScreen ? 'Yes' : 'No'}</p>
      <p>Medium Screen: {isMediumScreen ? 'Yes' : 'No'}</p>
      <p>Small Screen: {isSmallScreen ? 'Yes' : 'No'}</p>
    </div>
  );
}

注意点

  • メディアクエリの複雑さを最小限に抑え、コードの保守性を高めるように心掛けましょう。
  • SSR環境では、特にデフォルト値やnoSsrオプションを活用して、サーバーとクライアント間の不一致を回避してください。

これらのトラブルシューティングを活用することで、useMediaQueryを安定的に動作させることができます。次は、応用例とベストプラクティスについて解説します。

useMediaQueryの応用例とベストプラクティス

useMediaQueryフックを活用すると、単なる画面サイズの検知にとどまらず、アプリケーションの柔軟性やパフォーマンスを向上させることができます。このセクションでは、応用例を通じてベストプラクティスを解説します。

応用例

1. ユーザーインターフェースのモード切り替え

useMediaQueryを利用して、アプリケーションのデザインを動的に切り替えることができます。

例: ダークモードの自動切り替え

import React from 'react';
import { useMediaQuery } from '@mui/material';

function DarkModeToggle() {
  const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');

  return (
    <div style={{ backgroundColor: prefersDarkMode ? '#333' : '#fff', color: prefersDarkMode ? '#fff' : '#000' }}>
      <p>現在のモード: {prefersDarkMode ? 'ダークモード' : 'ライトモード'}</p>
    </div>
  );
}

export default DarkModeToggle;

この例では、ユーザーのブラウザ設定に応じてダークモードやライトモードを自動適用しています。

2. アニメーションの最適化

画面サイズに応じてアニメーションを有効または無効にすることで、パフォーマンスを最適化できます。

例: 小さい画面でアニメーションを無効化

function ResponsiveAnimation() {
  const isLargeScreen = useMediaQuery('(min-width:600px)');

  return (
    <div style={{ transition: isLargeScreen ? 'all 0.3s ease' : 'none' }}>
      <p>アニメーションは{isLargeScreen ? '有効' : '無効'}です。</p>
    </div>
  );
}

3. マルチカラムフォームの設計

画面サイズに応じてフォームのレイアウトを変更することで、ユーザーの入力体験を向上させます。

例: 画面幅に応じたフォームデザイン

function ResponsiveForm() {
  const isLargeScreen = useMediaQuery('(min-width:900px)');

  const formStyle = {
    display: 'grid',
    gridTemplateColumns: isLargeScreen ? '1fr 1fr' : '1fr',
    gap: '20px',
  };

  return (
    <form style={formStyle}>
      <input type="text" placeholder="First Name" />
      <input type="text" placeholder="Last Name" />
      <input type="email" placeholder="Email" />
      <textarea placeholder="Message" />
    </form>
  );
}

ベストプラクティス

1. テーマのブレークポイントを活用する

Material-UIのテーマを使うと、アプリ全体で一貫性のあるブレークポイントを利用できます。

例: テーマを用いた管理

import { useTheme } from '@mui/material/styles';
import { useMediaQuery } from '@mui/material';

function ThemeBreakpointsExample() {
  const theme = useTheme();
  const isTablet = useMediaQuery(theme.breakpoints.between('sm', 'md'));

  return (
    <p>{isTablet ? 'タブレットサイズです。' : 'タブレットサイズではありません。'}</p>
  );
}

2. 複雑なロジックは関数に分離する

画面サイズの条件分岐が複雑な場合は、関数を定義してロジックを整理しましょう。

例: カスタムフックで条件を抽象化

function useResponsiveConfig() {
  const isLargeScreen = useMediaQuery('(min-width:900px)');
  const isMediumScreen = useMediaQuery('(min-width:600px) and (max-width:899px)');
  const isSmallScreen = useMediaQuery('(max-width:599px)');

  return { isLargeScreen, isMediumScreen, isSmallScreen };
}

function ResponsiveComponent() {
  const { isLargeScreen, isMediumScreen, isSmallScreen } = useResponsiveConfig();

  if (isLargeScreen) return <p>大画面用のコンポーネント</p>;
  if (isMediumScreen) return <p>中画面用のコンポーネント</p>;
  return <p>小画面用のコンポーネント</p>;
}

3. パフォーマンスを意識した設計

  • 不要な再レンダリングを防ぐため、条件に依存するロジックを最小限にします。
  • 必要に応じてmemouseCallbackを使用して、効率的なレンダリングを行います。

まとめ

useMediaQueryは、単に画面サイズを判定するだけでなく、ユーザー体験やアプリのパフォーマンスを向上させるために役立つ強力なツールです。これらの応用例とベストプラクティスを活用して、より柔軟で効率的なReactアプリケーションを構築しましょう。

まとめ

本記事では、ReactのuseMediaQueryフックを使用してコンポーネントを画面サイズや条件に応じて切り替える方法を詳しく解説しました。基本的な使い方から、ブレークポイントの設定、実践的な例としてヘッダーのレイアウト切り替えやカードコンポーネントのグリッド切り替え、さらにダークモードやアニメーション最適化などの応用例を取り上げました。

useMediaQueryを適切に活用することで、レスポンシブデザインの実現が容易になり、ユーザー体験の向上やアプリケーションの柔軟性を大幅に高めることができます。これらの知識を活かして、より直感的で魅力的なReactアプリケーションを構築してみてください。

コメント

コメントする

目次