Reactでウィンドウサイズを監視するカスタムフックの作成方法と応用例

Reactでウィンドウサイズを監視するカスタムフックを作成することで、レスポンシブデザインをより簡潔かつ効果的に実現できます。Webアプリケーションでは、ウィンドウサイズの変化に応じたコンテンツの動的な表示が求められる場面が多くあります。しかし、各コンポーネントごとにリサイズイベントを処理するコードを書くのは冗長で非効率です。本記事では、Reactのカスタムフックを活用して、効率的にウィンドウサイズを監視する方法を具体例とともに解説します。初心者でも実装しやすいよう、基礎から応用までを丁寧に説明します。

目次

ウィンドウサイズ監視の必要性


現代のWebアプリケーションでは、画面サイズに応じた動的なコンテンツの調整が不可欠です。スマートフォン、タブレット、デスクトップといった多様なデバイスに対応するために、ウィンドウサイズの監視が必要となります。

ユースケース

  • レスポンシブデザイン:画面幅に応じて表示レイアウトを変更する。例えば、ナビゲーションメニューを横並びからハンバーガーメニューに切り替える。
  • パフォーマンス最適化:広い画面では高解像度画像、小さい画面では低解像度画像を動的に切り替える。
  • インタラクティブなUI:画面サイズに基づいて、コンテンツの表示・非表示を切り替える。

手動管理の課題

  • 冗長なコード:各コンポーネントでリサイズイベントを監視するコードを個別に実装する必要がある。
  • メモリリークのリスク:適切にイベントリスナーを削除しないと、メモリリークが発生する可能性がある。
  • メンテナンス性の低下:コードが複雑化し、保守が難しくなる。

こうした課題を解決するために、カスタムフックを活用することで効率的な開発が可能になります。

必要な環境と準備

カスタムフックを作成する前に、React環境のセットアップと必要なツールを整えましょう。以下に準備の手順を説明します。

Reactプロジェクトのセットアップ

  1. Node.jsのインストール
    ReactプロジェクトのセットアップにはNode.jsが必要です。公式サイトから最新版をインストールしてください。
  2. Reactプロジェクトの作成
    ターミナルで以下のコマンドを実行して、Reactアプリを作成します:
   npx create-react-app window-size-monitor
   cd window-size-monitor

必要なライブラリ


基本的なウィンドウサイズ監視ではReactの標準APIだけで十分ですが、開発を効率化するために以下のライブラリも便利です:

  • lodash(オプション):頻繁なリサイズイベントをデバウンスするために使用します。
  npm install lodash

プロジェクト構成


プロジェクトに以下のディレクトリを用意します:

  • src/hooks/:カスタムフックを格納するフォルダ。
  • src/components/:フックを利用するReactコンポーネントを格納するフォルダ。

初期設定の確認


Reactアプリが正常に起動することを確認します:

npm start

ブラウザでhttp://localhost:3000を開き、「React App」の表示が確認できれば準備完了です。

これでカスタムフック作成のための環境が整いました。次に、具体的な作成手順に進みます。

カスタムフック作成の基礎

カスタムフックは、Reactの機能(状態管理やライフサイクル)を再利用可能な形でカプセル化するための仕組みです。これにより、コンポーネント間で共通のロジックを簡単に共有できます。以下では、カスタムフック作成の基本的な流れを解説します。

カスタムフックの特徴

  • 関数として実装:カスタムフックは関数として定義され、useで始まる名前を持ちます(例:useWindowSize)。
  • 状態とライフサイクルの活用useStateuseEffectなどのReactフックを活用して、状態管理や副作用処理を行います。
  • 再利用可能:一度作成したカスタムフックは、複数のコンポーネントで再利用できます。

カスタムフックの基本構造


以下はカスタムフックの基本的な雛形です:

import { useState, useEffect } from 'react';

function useCustomHook() {
    const [value, setValue] = useState(initialValue);

    useEffect(() => {
        // 副作用の処理
        return () => {
            // クリーンアップ処理
        };
    }, [dependencies]);

    return value;
}

export default useCustomHook;

ウィンドウサイズを監視するカスタムフックの基本骨格


ウィンドウサイズ監視に特化したカスタムフックの簡単な構造は以下のようになります:

import { useState, useEffect } from 'react';

function useWindowSize() {
    const [windowSize, setWindowSize] = useState({
        width: window.innerWidth,
        height: window.innerHeight,
    });

    useEffect(() => {
        const handleResize = () => {
            setWindowSize({
                width: window.innerWidth,
                height: window.innerHeight,
            });
        };

        window.addEventListener('resize', handleResize);

        // クリーンアップ
        return () => {
            window.removeEventListener('resize', handleResize);
        };
    }, []);

    return windowSize;
}

export default useWindowSize;

次のステップ


ここではカスタムフックの基礎を説明しました。この基礎をもとに、次のステップでuseEffectを用いたイベントリスナーの設定方法をさらに詳しく解説します。

useEffectを用いたイベントリスナーの設定

ウィンドウサイズを監視するカスタムフックでは、useEffectを活用してリサイズイベントを監視します。このセクションでは、useEffectを利用して適切にイベントリスナーを設定し、クリーンアップ処理を行う方法を詳しく解説します。

useEffectの基本概念


useEffectは、副作用(サイドエフェクト)を管理するReactのフックです。以下の用途に使用されます:

  • コンポーネントのマウント時に初期化処理を実行する。
  • 状態やプロパティの変更時に特定の処理を実行する。
  • コンポーネントのアンマウント時にクリーンアップ処理を行う。

リサイズイベントの設定


リサイズイベントを監視するためのコード例を以下に示します:

import { useState, useEffect } from 'react';

function useWindowSize() {
    const [windowSize, setWindowSize] = useState({
        width: window.innerWidth,
        height: window.innerHeight,
    });

    useEffect(() => {
        const handleResize = () => {
            setWindowSize({
                width: window.innerWidth,
                height: window.innerHeight,
            });
        };

        // イベントリスナーの登録
        window.addEventListener('resize', handleResize);

        // クリーンアップ処理
        return () => {
            window.removeEventListener('resize', handleResize);
        };
    }, []); // 空の依存配列で一度だけ実行

    return windowSize;
}

export default useWindowSize;

コードのポイント

  1. イベントリスナーの登録
    window.addEventListenerを使用して、リサイズイベントが発生したときにhandleResizeが実行されるようにします。
  2. クリーンアップ処理
    useEffect内で返された関数は、コンポーネントのアンマウント時に実行されます。このタイミングでwindow.removeEventListenerを呼び出して、不要なイベントリスナーを削除します。
  3. 依存配列の設定
    第2引数に空の配列([])を渡すことで、useEffectが初回レンダリング時にのみ実行されるようにします。

クリーンアップ処理の重要性


適切なクリーンアップ処理を行わない場合、以下の問題が発生する可能性があります:

  • メモリリーク:不要なリスナーがメモリを消費し続ける。
  • パフォーマンスの低下:複数のリスナーが不要に呼び出されることでアプリのパフォーマンスが低下する。

次のステップ


この段階で、ウィンドウサイズを監視するための基本的なuseEffectの設定が完了しました。次は、カスタムフックの完成版コードをさらに詳しく解説します。

カスタムフックの完全なコード例

ここでは、ウィンドウサイズを監視するカスタムフックの完全な実装コードを紹介し、その動作について詳しく解説します。このコードは、実際にReactアプリケーション内で使用できるものです。

完成コード


以下は、ウィンドウサイズを監視するカスタムフックの完成版コードです:

import { useState, useEffect } from 'react';

function useWindowSize() {
    // ウィンドウサイズを管理する状態を定義
    const [windowSize, setWindowSize] = useState({
        width: window.innerWidth,
        height: window.innerHeight,
    });

    useEffect(() => {
        // リサイズイベント時のコールバック関数
        const handleResize = () => {
            setWindowSize({
                width: window.innerWidth,
                height: window.innerHeight,
            });
        };

        // イベントリスナーを登録
        window.addEventListener('resize', handleResize);

        // クリーンアップ処理でイベントリスナーを解除
        return () => {
            window.removeEventListener('resize', handleResize);
        };
    }, []); // 依存配列は空で、一度だけ設定

    return windowSize; // 現在のウィンドウサイズを返す
}

export default useWindowSize;

コードの解説

  1. useStateによる状態管理
  • 初期状態でwindow.innerWidthwindow.innerHeightを取得し、現在のウィンドウサイズを状態として管理します。
  1. useEffectによるイベントリスナーの設定
  • リサイズイベントが発生するたびにhandleResizeが実行され、状態が更新されます。
  • アンマウント時にはイベントリスナーを削除して、メモリリークを防ぎます。
  1. カスタムフックの出力
  • フックを呼び出したコンポーネントで簡単に使用できるよう、windowSizeオブジェクト({width, height})を返します。

実際の動作例


このカスタムフックを使用すると、次のようなシンプルなReactコンポーネントが作成できます:

import React from 'react';
import useWindowSize from './hooks/useWindowSize';

function ResponsiveComponent() {
    const { width, height } = useWindowSize();

    return (
        <div>
            <p>ウィンドウの幅: {width}px</p>
            <p>ウィンドウの高さ: {height}px</p>
        </div>
    );
}

export default ResponsiveComponent;

コードの動作確認

  1. ブラウザでページを開き、ウィンドウサイズを変更します。
  2. リサイズイベントが発生すると、幅と高さの表示がリアルタイムに更新されます。

次のステップ


このカスタムフックを利用することで、ウィンドウサイズを監視する複雑なコードが不要になります。次に、実際の使用例を掘り下げて解説します。

作成したカスタムフックの使用例

ここでは、先ほど作成したuseWindowSizeフックを活用して、実際のReactコンポーネントでの使用例を解説します。これにより、レスポンシブデザインや動的レイアウトを簡単に実現できます。

使用例1: ウィンドウサイズのリアルタイム表示


この例では、ウィンドウサイズをリアルタイムで画面に表示するシンプルなコンポーネントを作成します。

import React from 'react';
import useWindowSize from './hooks/useWindowSize';

function WindowSizeDisplay() {
    const { width, height } = useWindowSize();

    return (
        <div style={{ padding: '20px', fontSize: '1.2rem' }}>
            <p>現在のウィンドウ幅: <strong>{width}px</strong></p>
            <p>現在のウィンドウ高さ: <strong>{height}px</strong></p>
        </div>
    );
}

export default WindowSizeDisplay;

動作説明:

  • ウィンドウサイズを変更するたびに、画面上の幅と高さがリアルタイムで更新されます。
  • このコンポーネントは、情報表示の基本的なユースケースをカバーします。

使用例2: レスポンシブデザインのレイアウト切り替え


次に、ウィンドウ幅に応じて表示内容を動的に切り替える例を示します。

import React from 'react';
import useWindowSize from './hooks/useWindowSize';

function ResponsiveLayout() {
    const { width } = useWindowSize();

    return (
        <div style={{ padding: '20px', fontSize: '1.2rem' }}>
            {width > 768 ? (
                <p>大画面向けレイアウトが表示されています。</p>
            ) : (
                <p>小画面向けレイアウトが表示されています。</p>
            )}
        </div>
    );
}

export default ResponsiveLayout;

動作説明:

  • ウィンドウ幅が768pxを超える場合は「大画面向けレイアウト」、それ以下の場合は「小画面向けレイアウト」が表示されます。
  • レスポンシブデザインの基本構造として利用できます。

使用例3: コンポーネントのスタイリングに応用


ウィンドウサイズを監視してスタイリングを動的に変更する例です。

import React from 'react';
import useWindowSize from './hooks/useWindowSize';

function StyledBox() {
    const { width } = useWindowSize();

    const boxStyle = {
        width: '100%',
        height: '100px',
        backgroundColor: width > 500 ? 'lightblue' : 'lightcoral',
        textAlign: 'center',
        lineHeight: '100px',
        fontSize: '1.5rem',
        color: 'white',
    };

    return <div style={boxStyle}>ウィンドウ幅: {width}px</div>;
}

export default StyledBox;

動作説明:

  • ウィンドウ幅が500pxを超えると背景色が青、それ以下では赤に切り替わります。
  • デザインの調整や視覚的なフィードバックに役立つ例です。

次のステップ


これらの例を通じて、useWindowSizeフックを利用したウィンドウサイズ監視の利便性が理解できたと思います。次は、エラーハンドリングやパフォーマンス最適化について解説します。

エラーハンドリングと最適化

作成したカスタムフックuseWindowSizeをより安全で効率的に運用するために、エラーハンドリングとパフォーマンス最適化を導入します。これにより、アプリケーションの信頼性とスムーズな動作を確保できます。

エラーハンドリングの実装

ウィンドウサイズ取得中にエラーが発生する可能性は低いですが、特定の状況(サーバーサイドレンダリングやブラウザ非互換など)で問題が生じる場合があります。これを防ぐために以下の処理を追加します:

import { useState, useEffect } from 'react';

function useWindowSize() {
    const [windowSize, setWindowSize] = useState({
        width: undefined,
        height: undefined,
    });

    useEffect(() => {
        // サーバーサイドレンダリング対応のチェック
        if (typeof window === 'undefined') {
            console.warn('Windowオブジェクトが利用できません。');
            return;
        }

        const handleResize = () => {
            try {
                setWindowSize({
                    width: window.innerWidth,
                    height: window.innerHeight,
                });
            } catch (error) {
                console.error('ウィンドウサイズの取得に失敗しました:', error);
            }
        };

        // 初期サイズを設定
        handleResize();

        // リスナーの登録
        window.addEventListener('resize', handleResize);

        // クリーンアップ処理
        return () => {
            window.removeEventListener('resize', handleResize);
        };
    }, []);

    return windowSize;
}

export default useWindowSize;

ポイント:

  1. サーバーサイドレンダリング対応
  • windowオブジェクトが利用できない環境(Next.jsのSSRなど)に対応するため、typeof windowをチェックします。
  1. エラーログ
  • 例外発生時にconsole.errorでログを出力し、原因を特定しやすくします。

パフォーマンス最適化

リサイズイベントは高頻度で発生するため、過剰な状態更新を防ぐ必要があります。以下のようにデバウンス処理を導入することで、効率的な動作を実現します。

import { useState, useEffect } from 'react';
import debounce from 'lodash/debounce';

function useWindowSize() {
    const [windowSize, setWindowSize] = useState({
        width: window.innerWidth,
        height: window.innerHeight,
    });

    useEffect(() => {
        const handleResize = debounce(() => {
            setWindowSize({
                width: window.innerWidth,
                height: window.innerHeight,
            });
        }, 200); // デバウンスの遅延時間を200msに設定

        // リスナーの登録
        window.addEventListener('resize', handleResize);

        // クリーンアップ処理
        return () => {
            window.removeEventListener('resize', handleResize);
            handleResize.cancel(); // デバウンス関数のキャンセル
        };
    }, []);

    return windowSize;
}

export default useWindowSize;

ポイント:

  1. デバウンス処理
  • lodashdebounce関数を使用して、リサイズイベントの頻度を制御します。
  • イベントが連続して発生した場合でも、一定間隔(200ms)でのみ状態更新を行います。
  1. クリーンアップの徹底
  • デバウンス関数のキャンセル処理を明示的に実行して、不要な処理を完全に停止します。

最適化の効果

  • パフォーマンス向上:状態更新の頻度を抑えることで、CPU負荷が軽減されます。
  • 信頼性の向上:予期しないエラーを回避し、安全にフックを運用できます。

次のステップ


ここで紹介したエラーハンドリングと最適化を適用すれば、カスタムフックの品質が向上します。次に、このフックの応用例とレスポンシブデザインへの活用方法について詳しく解説します。

応用例とレスポンシブデザインの活用

作成したuseWindowSizeフックは、レスポンシブデザインや動的コンテンツ管理の幅広いユースケースに応用できます。このセクションでは、実際のプロジェクトでの応用例を紹介し、レスポンシブデザインの強化に役立つ具体的な方法を解説します。

応用例1: レスポンシブナビゲーションメニュー

ウィンドウ幅に応じて、ナビゲーションメニューを切り替える例です。

import React from 'react';
import useWindowSize from './hooks/useWindowSize';

function ResponsiveNav() {
    const { width } = useWindowSize();

    return (
        <nav>
            {width > 768 ? (
                <ul style={{ display: 'flex', listStyle: 'none', gap: '10px' }}>
                    <li>ホーム</li>
                    <li>サービス</li>
                    <li>お問い合わせ</li>
                </ul>
            ) : (
                <button
                    style={{
                        background: 'blue',
                        color: 'white',
                        padding: '10px',
                        border: 'none',
                    }}
                >
                    メニューを開く
                </button>
            )}
        </nav>
    );
}

export default ResponsiveNav;

動作説明:

  • 幅が768pxを超える場合、横並びのナビゲーションメニューを表示します。
  • 幅が768px以下の場合、ハンバーガーメニュー形式のボタンを表示します。

応用例2: コンテンツの動的レンダリング

ウィンドウサイズに基づいて、表示する画像やコンテンツを動的に変更する例です。

import React from 'react';
import useWindowSize from './hooks/useWindowSize';

function ResponsiveImages() {
    const { width } = useWindowSize();

    const imageUrl = width > 1024 
        ? 'high-resolution.jpg' 
        : 'low-resolution.jpg';

    return (
        <img 
            src={imageUrl} 
            alt="レスポンシブ画像" 
            style={{ width: '100%', height: 'auto' }}
        />
    );
}

export default ResponsiveImages;

動作説明:

  • ウィンドウ幅が1024pxを超える場合は高解像度の画像を、以下の場合は低解像度の画像を表示します。
  • デバイスごとの最適化が可能で、パフォーマンス向上に寄与します。

応用例3: グリッドレイアウトの調整

ウィンドウサイズに応じてグリッドの列数を変更する例です。

import React from 'react';
import useWindowSize from './hooks/useWindowSize';

function ResponsiveGrid() {
    const { width } = useWindowSize();

    const columns = width > 1200 ? 4 : width > 768 ? 2 : 1;

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

    return (
        <div style={gridStyle}>
            <div style={{ background: 'lightgray', padding: '20px' }}>アイテム1</div>
            <div style={{ background: 'lightgray', padding: '20px' }}>アイテム2</div>
            <div style={{ background: 'lightgray', padding: '20px' }}>アイテム3</div>
            <div style={{ background: 'lightgray', padding: '20px' }}>アイテム4</div>
        </div>
    );
}

export default ResponsiveGrid;

動作説明:

  • ウィンドウ幅が1200pxを超える場合は4列、768px以上1200px以下の場合は2列、それ以下では1列のグリッドを表示します。
  • レスポンシブなグリッドデザインが簡単に実現できます。

応用例4: カスタマイズされたユーザー体験

ウィンドウサイズに基づいて、UI要素を動的に切り替えることで、ユーザー体験を最適化できます。
例えば:

  • 幅が狭い場合はテキストを簡潔にし、幅が広い場合は詳細情報を表示する。
  • デスクトップ向けには詳細なメニュー、モバイル向けには最小限のメニューを表示する。

レスポンシブデザイン強化のポイント

  • カスタムフックを利用することで、CSSメディアクエリに依存しすぎない設計が可能になります。
  • useWindowSizeと組み合わせて、ビジネスロジックを含む動的デザインを実現できます。

次のステップ


カスタムフックuseWindowSizeを応用した実践例を通じて、レスポンシブデザインの可能性を広げる方法を学びました。次は、本記事の内容をまとめます。

まとめ

本記事では、Reactでウィンドウサイズを監視するカスタムフックuseWindowSizeの作成方法を解説しました。ウィンドウサイズに応じてコンテンツを動的に変更したり、レスポンシブデザインを実現したりするための基礎から応用までをカバーしました。

主要なポイントとして、以下が挙げられます:

  1. カスタムフックの作成: useStateuseEffectを利用して、ウィンドウサイズを監視し、リサイズイベントに対応するフックを作成しました。
  2. エラーハンドリングと最適化: サーバーサイドレンダリング対応やデバウンス処理を取り入れ、アプリケーションの信頼性とパフォーマンスを向上させました。
  3. 応用例の紹介: ナビゲーションメニューの切り替え、画像の動的表示、グリッドレイアウトの調整など、実際のユースケースでフックを活用する方法を紹介しました。

useWindowSizeを使えば、ウィンドウサイズの変更に即座に反応するコンポーネントを簡単に作成でき、ユーザーに最適な体験を提供することができます。この記事で紹介したテクニックを活用して、より柔軟で動的なReactアプリケーションを構築してください。

コメント

コメントする

目次