【Reactフック】useContextによるコンテキストの共有

JavaScript

Reactでは、コンポーネント間でデータを共有する際に、propsを使う方法だけでなく、より効率的で便利な方法としてコンテキストAPIを利用することができます。

この記事では、useContext フックを使ってコンテキストを共有する方法について詳しく解説します。
具体例を交えながら、useContext の使い方、利点、ベストプラクティスについて見ていきましょう。

useContextとは

useContext は React のフックの一つで、コンポーネントツリー全体にわたってデータを共有するために使用されます。
これにより、「propsを次々と深いコンポーネント階層へバケツリレーする(以降:Prop Drilling)」のを避けることができます。

深すぎるProp Drillingには、以下のような問題があります。

  • propsに変更が生じた際に、バケツリレーに関係する全てのコンポーネントの修正が必要になる
  • 最下層のコンポーネントに到達するまでの途中にあるコンポーネントに、不要なpropsが渡る場合がある

基本的な使い方

useContextの基本的な使い方は次の4ステップです。

  1. コンテキストを作成する(UserContext.jsx
  2. コンテキストプロバイダーを使って値を提供する(UserProvider.jsx
  3. コンテキストの値を取得する(UserProfile.jsx
  4. コンテキストプロバイダーを使ってコンポーネントをラップする(App.jsx

サンプルコードと解説

ステップ1で、共有したいデータを管理するためのコンテキストを作成します。以下のコードでは、UserContext というコンテキストを作成しています。

UserContext React.createContext メソッドによって生成されるオブジェクトであり、具体的には「コンテキストオブジェクト」と呼ばれます。このオブジェクトは、次の2つの主要プロパティを持っています。

  1. Provider: コンテキストの値を提供するコンポーネント。これを使用して、コンテキストの値を子コンポーネントに渡します。
  2. Consumer: コンテキストの値を取得するコンポーネント。ただし、現在は useContext フックを使用することが一般的です。
UserContext.jsx
// コンテキストを作成する UserContext.jsx
import React from 'react';

const UserContext = React.createContext(null);

export default UserContext;

ステップ2でコンテキストプロバイダーを使い、コンテキストの値を提供するコンポーネントを作成します。ここでは、UserProvider というコンポーネントを作成し、「value={user}」の記述によりユーザー情報を値として提供(provide)しています。

UserProvider.jsx
// UserProvider.jsx
import React, { useState } from 'react';
import UserContext from './UserContext';

function UserProvider({ children }) {
  const [user, setUser] = useState({ name: 'Luke Skywalker' });

  return (
    <UserContext.Provider value={user}>
      {children}
    </UserContext.Provider>
  );
}

export default UserProvider;

コンテキストの値を取得するために、ステップ3でuseContext フックを使います。
Provider(提供者)に対し、useContextを使用するコンポーネントはConsumer(消費者)と呼ばれます。

以下のコードでは、UserProfile コンポーネントが UserContext の値を取得し、ユーザー名を表示しています。

UserProfile.jsx
// UserProfile.jsx
import React, { useContext } from 'react';
import UserContext from './UserContext';

function UserProfile() {
  const user = useContext(UserContext);

  return (
    <div>
      {user ? <p>Welcome, {user.name}!</p> : <p>Please log in.</p>}
    </div>
  );
}

export default UserProfile;

App コンポーネントで UserProvider を使って、アプリケーション全体をラップします。

UserProfile コンポーネントは、UserProviderの「childrenpropsとして渡されるため、 UserContext にアクセスできるようになり、useContextを介してUserContext.Providervalueとして提供されているユーザー情報を取得できます。

App.jsx
// App.jsx
import React from 'react';
import UserProvider from './UserProvider';
import UserProfile from './UserProfile';

function App() {
  return (
    <UserProvider>
      <UserProfile />
    </UserProvider>
  );
}

export default App;

よくある使い方と実践的なサンプル

ユーザーの認証情報をコンポーネント全体に共有する

ログイン状態やユーザー情報を一箇所で管理し、必要なコンポーネントで利用する場合のサンプルプログラムです。

AuthContext.jsx
import React from 'react';

const AuthContext = React.createContext(null);

export default AuthContext;
AuthProvider.jsx
import React, { useState } from 'react';
import AuthContext from './AuthContext';

function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  const login = (username) => setUser({name: username});
  const logout = () => setUser(null);

  return (
    <AuthContext.Provider value={ {user, login, logout} }>
      {children}
    </AuthContext.Provider>
  );
}

export default AuthProvider;
UserProfile.jsx
import React, { useState, useContext } from 'react';
import AuthContext from './AuthContext';

function UserProfile() {
  const {user, login, logout} = useContext(AuthContext);
  const [username, setUsername] = useState('');

  return (
    <div>
      {user ? (
      <>
        <p>Welcome, {user.name}!</p>
        <button onClick={logout}>logout</button>
      </>
      ) : (
      <>
        <p>Please log in.</p>
        <input type="text"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
        />
        <button onClick={() => login(username)}>login</button>
      </>
      )}
    </div>
  );
}

export default UserProfile;
App.jsx
import React from 'react';
import AuthProvider from './AuthProvider';
import UserProfile from './UserProfile';

function App() {
  return (
    <AuthProvider>
      <UserProfile />
    </AuthProvider>
  );
}

export default App;

このサンプルプログラムでは、AuthContext を使用してユーザーの認証情報を共有しています。

AuthProvider コンポーネントがloginおよびlogout機能を関数として提供し、UserProfile コンポーネントでその情報を使用しています。

各ファイルの繋がりを意識しながら実際にプログラミングして、表示と動作を確認してみてください。

言語設定やテーマ設定を管理する

ユーザーの選択によってテーマや言語をアプリケーション全体で統一する場合のサンプルプログラムです。

ThemeContext.jsx
import React from 'react';

// デフォルトのテーマを「ライト」で設定
const ThemeContext = React.createContext('light');

export default ThemeContext;
ThemeProvider.jsx
import React, { useState } from 'react';
import ThemeContext from './ThemeContext';

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  // テーマを切り替える関数
  const toggleTheme = () => {
    setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
  };

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

export default ThemeProvider;
ThemedComponent.jsx
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext ';

function ThemedComponent() {
  const {theme, toggleTheme} = useContext(ThemeContext );

  return (
    <div>
      <button className={theme} onClick={toggleTheme}>
        テーマ切替
      </button>
      <p style={
        {
          color: theme === 'light' ? '#000' : '#fff',
          backgroundColor: theme === 'light' ? '#fff' : '#000'
        }
      }>
        Current theme is {theme}
      </p>
    </div>
  );
}

export default ThemedComponent;
App.jsx
import React from 'react';
import ThemeProvider from './ThemeProvider';
import ThemedComponent from './ThemedComponent';

function App() {
  return (
    <ThemeProvider>
      <ThemedComponent />
    </ThemeProvider>
  );
}

export default App;

このサンプルプログラムでは、ThemeContext を使用してテーマ設定を管理しています。
ThemeProvider コンポーネントがテーマの切り替え機能を提供し、ThemedComponent コンポーネントでその機能を使用しています。

ThemedComponent コンポーネントは、 theme に応じて段落の文字色と内容を変更します。これにより、テーマの切り替えがブラウザのページで視覚的に確認できるようになります。

各ファイルの繋がりを意識しながら実際にプログラミングして、表示と動作を確認してみてください。

ベストプラクティスと注意点

ベストプラクティス

  • 適切なコンテキストの分離
    • すべてのグローバルな状態を1つのコンテキストで管理するのではなく、必要に応じて複数のコンテキストを作成することが推奨されます。
      例えば、ユーザー情報、テーマ設定、言語設定などを個別のコンテキストで管理することで、コードの可読性とメンテナンス性が向上します。
  • 必要最小限の値をコンテキストに含める
    • コンテキストに含める値は必要最小限に留めることが重要です。
      例えば、関数の中で使用される一時的な値や頻繁に変わる値はコンテキストに含めず、ローカルのstateで管理する方がベターです。
  • コンポーネントの再利用性を考慮する
    • コンテキストを使用するコンポーネントは、再利用性を考慮して設計しましょう。
      コンテキストに依存しない部分は、独立したコンポーネントとして分離することで、他のプロジェクトや機能にも再利用できます。

注意点

  • コンテキストの更新の最小化
    • コンテキストの更新は全てのコンシューマーに影響を与えるため、頻繁に更新される値は避け、必要最小限の値を持つようにしましょう。
      例えば、頻繁に変わるカウンタ値などはコンテキストに含めず、ローカルのstateで管理します。
  • デフォルト値の設定
    • コンテキストを作成する際には、デフォルト値を設定しておくことが推奨されます。これにより、コンテキストプロバイダーが提供されない場合でも、コンシューマーが予期しないエラーを防ぐことができます。
  • コンテキストの過剰使用を避ける
    • 全てのデータ共有にコンテキストを使用すると、コードが複雑になり、管理が難しくなります。
      コンテキストは、本当に必要な場合にのみ使用し、可能な限りローカルのstatepropsでデータを管理するようにしましょう。

まとめ

useContext フックを利用することで、Reactコンポーネント間でデータを効率的に共有し、深いProp Drillingを避けることができます。

また、グローバルな設定やユーザー情報などの共有が容易になり、アプリケーションの可読性とメンテナンス性の向上が期待できます。

useContext を活用してより洗練されたReactアプリケーションを作成するために、この記事が少しでも参考になれば幸いです。

本記事についての質問、誤りの指摘、ご意見ご感想などありましたら、ぜひお気軽にコメントください。

それでは、最後までお読みいただき、ありがとうございます。

コメント

タイトルとURLをコピーしました