🐕

UseContextの基本的な使い方

2024/07/17に公開

参考

https://qiita.com/seira/items/8a170cc950241a8fdb23

はじめに

ReactのContextAPIは、Reactアプリケーション全体でデータを簡単に共有するための機能を提供しています。特に、プロップスドリリング(深い階層のコンポーネント間でプロップスを渡すこと)の問題を解決するのに役立ちます。この記事では、ContextAPIの基本的な使用方法と、stateと更新関数を分割したContextを利用した実践的な例を通じてその利点を説明します。

ContextAPIの基本的な使用方法

1. Contextの作成と分割

まず、必要なデータごとにContextを作成します。ここでは、UserStateContextUserDispatchContextを作成します。

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

// State用のContextを作成します
const UserStateContext = createContext();
// 更新関数用のContextを作成します
const UserDispatchContext = createContext();

2. Context Providerの定義

次に、各ContextのProviderを作成し、共有したい値を提供します。

// UserProviderコンポーネントを定義します
const UserProvider = ({ children }) => {
  const [user, setUser] = useState({ name: 'John Doe', age: 30 });

  return (
    <UserStateContext.Provider value={user}>
      <UserDispatchContext.Provider value={setUser}>
        {children}
      </UserDispatchContext.Provider>
    </UserStateContext.Provider>
  );
};

// 各Contextを利用するカスタムフックを定義してエクスポートします
const useUserState = () => useContext(UserStateContext);
const useUserDispatch = () => useContext(UserDispatchContext);

export { UserProvider, useUserState, useUserDispatch };

3. Contextの使用

次に、useUserStateuseUserDispatchを使用して、必要なコンポーネントでContextの値にアクセスします。<button>部分をコンポーネント化し、更新関数だけを参照するコンポーネントにします。

import React from 'react';
import { useUserState, useUserDispatch } from './UserProvider';

const UserProfile = () => {
  const user = useUserState();

  return (
    <div>
      <p>Name: {user.name}</p>
      <p>Age: {user.age}</p>
      <IncreaseAgeButton />
    </div>
  );
};

const IncreaseAgeButton = () => {
  const setUser = useUserDispatch();

  return (
    <button onClick={() => setUser(prevUser => ({ ...prevUser, age: prevUser.age + 1 }))}>
      Increase Age
    </button>
  );
};

export default UserProfile;

4. アプリケーションにProviderを追加

最後に、アプリケーションにProviderを追加します。

import React from 'react';
import ReactDOM from 'react-dom';
import { UserProvider } from './UserProvider';
import UserProfile from './UserProfile';

const App = () => (
  <UserProvider>
    <UserProfile />
  </UserProvider>
);

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

コンポーネントの再レンダリングについて

ContextAPIを使用する際の懸念の一つは、コンテキストの値が変更されるたびにコンシューマーコンポーネント[1]が再レンダリングされることです。これがパフォーマンスに悪影響を与えることがあります。stateと更新関数を分割したContextを利用することで、必要な部分だけが再レンダリングされるようにすることができます。

例えば、上記のコードでは<IncreaseAgeButton>コンポーネントは更新関数だけを参照しており、stateの変更に依存しません。そのため、ユーザー情報が変更されても<IncreaseAgeButton>コンポーネントは再レンダリングされません。

再レンダリングの防止策

  1. useCallback/useMemoを使用し、コールバック関数や計算結果をメモ化して、不要な再レンダリングを防ぎます。
  2. stateと更新関数を分割したContextを利用して、特定の部分だけが再レンダリングされるようにします。 (今回の例)

下記はuseCallbackuseMemoを使用して再レンダリングを最適化する方法です。

import React, { useCallback, useMemo } from 'react';
import { useUserState, useUserDispatch } from './UserProvider';

const UserProfile = () => {
  const user = useUserState(); // UserStateContextからユーザー情報を取得

  // userが変更されるたびに新しいJSX要素を計算します
  const renderedUser = useMemo(() => (
    <div>
      <p>Name: {user.name}</p>
      <p>Age: {user.age}</p>
    </div>
  ), [user]); // userが変更された場合のみ再計算

  return (
    <div>
      {renderedUser} {/* メモ化されたユーザー情報をレンダリング */}
      <IncreaseAgeButton /> {/* IncreaseAgeButtonコンポーネントをレンダリング */}
    </div>
  );
};

const IncreaseAgeButton = () => {
  const setUser = useUserDispatch(); // UserDispatchContextからユーザー情報を更新する関数を取得

  // setUserが変更されない限り新しいコールバック関数を作成しません
  const handleIncreaseAge = useCallback(() => {
    setUser(prevUser => ({ ...prevUser, age: prevUser.age + 1 }));
  }, [setUser]); // setUserが変更された場合のみ再作成

  return (
    <button onClick={handleIncreaseAge}>
      Increase Age
    </button>
  );
};

export default UserProfile;

まとめ

ReactのContextAPIは、アプリケーション全体でデータを簡単に共有するのに非常に便利です。stateと更新関数を分割したContextを利用することで、特定のデータに依存するコンポーネントのみが再レンダリングされるようにすることができ、パフォーマンスの最適化が図れます。特に、更新関数だけを参照するコンポーネントは、stateの変更に影響されず再レンダリングされないため、効率的なレンダリングが可能です。

脚注
  1. コンテキストを利用しているコンポーネント。今回でいうUserProfileIncreaseAgeButtonを指します。 ↩︎

Discussion