お嬢様が教えるuseContext()

## 教材

ShinCodeさんの

【**完全保存版】React Hooksを完全に理解するHooksマスター講座【React18~19対応】**

https://www.udemy.com/course/react-hooks-complete-course/?couponCode=ST19MT60324

を元に、私が学んだ内容をまとめていきますわ。

## useContext

```tsx
const value = useContext(SomeContext)
```

`useContext`は、Reactのコンポーネントでデータを深く受け渡す際に使用するフックですわ。コンポーネントの階層が深くなると、propsの受け渡しが複雑になりがちですの。その問題を解決するために、`Context`を使ってデータを一元管理し、必要なコンポーネントでデータを参照することができますの。

### **Propsのバケツリレー**

以下の図のように、深いコンポーネントツリーでデータを渡すために、propsを多くのコンポーネントを経由して渡す必要がありますの。

![スクリーンショット 2024-06-05 20.13.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983769/006e341a-48d8-e99c-44f5-b7ebec59a4d2.png)

### **Contextの使用**

`Context`を使用すると、データを一つの場所にまとめて管理し、任意のコンポーネントから参照できますの。

![スクリーンショット 2024-06-05 20.13.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983769/8b56f3ea-0655-dbfa-d24f-39c5e177d334.png)

### **ユースケース**

1. **ユーザー認証**
    
    ユーザー認証のために`Context`を利用することがよくございますわ。例えば、ログインしているユーザー情報を`Context`で管理し、ヘッダーなどのコンポーネントでその情報を参照して表示を変更することができますの。このような場合、`useContext`フックを使用して、どのページでもユーザー情報を取得できるようにします。
    <br>
    
2. **テーマの切り替え**
    
    ダークモードやライトモードの切り替えにも`Context`が有用でございます。テーマの状態を`Context`で管理することで、アプリケーション全体で統一されたテーマを簡単に適用することができますわ。
    

## 認証プロバイダーとしてのuseContext

```tsx
import { useState } from "react";

const Lesson4_1 = () => {
  const [username, setUsername] = useState("");
  const [email, setEmail] = useState("");

  const handleLogin = () => {};

  return (
    <div>
      <div>
        <p>ログイン済み:</p>
        <button>ログアウト</button>
      </div>

      <div>
        <input
          type="text"
          placeholder="Username"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
        />
        <input
          type="email"
          placeholder="Email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <button onClick={handleLogin}>ログイン</button>
      </div>
    </div>
  );
};

export default Lesson4_1;

```

ログインしている状態なら「ログイン済み」を表示し、それ以外ならフォームを表示させたいのですわね。

### useContext()で必要な要素

1. **Contextの作成**
    
    Reactでデータをグローバルに管理するための仕組みですわ。通常、データを親コンポーネントから子コンポーネントにpropsで渡しますが、コンポーネント階層が深くなった時に簡単にデータを渡すことができますの。
    
    ```tsx
    import { createContext } from 'react';
    
    // Contextの作成
    const AuthContext = createContext(null);
    ```
    <br>
2. **Providerの作成**
    
    Providerは、Contextで管理するデータをコンポーネントツリーに供給する役割を持つのですわ。これにより、アプリケーション全体や特定のコンポーネントにデータを渡すことができますの。
    
    このコードに関しては、次の章で詳しく解説しますわ。
    
    ```tsx
    import { ReactNode, createContext, useContext, useState } from "react"
    
    type User = {
      id: string;
      username: string;
      email: string
    }
    
    interface AuthContextType {
      user: User | null;
      login: (userInfo: User) => void;
      logout: () => void;
    }
    
    const AuthContext = createContext<AuthContextType | undefined>(undefined);
    
    export const useAuth = () => {
      const context = useContext(AuthContext);
      return context;
    }
    
    // プロバイダーを作成する
    const AuthProvider = ({children}: {children :ReactNode}) => {
      const [user, setUser] = useState<User | null>(null);
    
      // ログイン・ログアウトを仮に実装する
      const login = (userInfo: User) => {
        if(userInfo.username === "testUser" && userInfo.email === "test@gmail.com") {
          setUser(userInfo);
        }
        else {
          console.log("ログインできません")
        }
      };
      const logout = () => {
        setUser(null)
      };
      // childrenはreact.nodeのこと
    
      const contextValue = {
        user,
        login,
        logout
      }
    
      return (
        <AuthContext.Provider value={contextValue}>
          {children}
        </AuthContext.Provider>
      )
    }
    
    export default AuthProvider;
    ```
    <br>
3. **Providerでラップする**
    
    App全体をProviderでラップするのですわ。
    
    AuthProviderでuseContextを反映させたいコンポーネントを囲むのです。
    
    ```tsx
    import React from "react";
    import ReactDOM from "react-dom/client";
    import App from "./App.tsx";
    import "./index.css";
    import AuthProvider from "./lessons/Lesson4/context/AuthContext.tsx";
    
    ReactDOM.createRoot(document.getElementById("root")!).render(
      <React.StrictMode>
        <AuthProvider>
          <App />
        </AuthProvider>
      </React.StrictMode>
    );
    ```
    <br>
  
4. **useContextを使ってContextの値を取得する**
    
    作成したuseContextを使用したいコンポーネントで呼び出し、値を取得しますの。
    この場合だと、`const {user, login, logout} = userAuth()`の部分ですわ。
    
    ```tsx
    import { useState } from "react";
    import { useAuth } from "../context/AuthContext";
    
    const Lesson4_1 = () => {
      const [username, setUsername] = useState("");
      const [email, setEmail] = useState("");
    
      const { user, login, logout } = useAuth();
    
      const handleLogin = () => {
        login({ id: "1", username, email });
      };
    
      return (
        <div>
          {user ? (
            <div>
              <p>ログイン済み:</p>
              <button onClick={logout}>ログアウト</button>
            </div>
          ) : (
            <div>
              <input
                type="text"
                placeholder="Username"
                value={username}
                onChange={(e) => setUsername(e.target.value)}
              />
              <input
                type="email"
                placeholder="Email"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
              />
              <button onClick={handleLogin}>ログイン</button>
            </div>
          )}
        </div>
      );
    };
    
    export default Lesson4_1;
    
    ```
    ![スクリーンショット 2024-06-05 19.00.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983769/009e08fa-80f0-c864-2d6f-1ce3822f0076.png)
![スクリーンショット 2024-06-05 19.01.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983769/4255cc2d-7a1f-cdc7-b617-299e827b4de1.png)
    
    このように、`testUser`と`test@gmail.com`の場合にログインすることができ、ログアウトボタンを押すと`setUser`がnullになり、再度入力フォームに戻りますの。
    
    ## Providerのコード
    
    ```tsx
    import { ReactNode, createContext, useContext, useState } from "react"
    
    type User = {
      id: string;
      username: string;
      email: string
    }
    
    interface AuthContextType {
      user: User | null;
      login: (userInfo: User) => void;
      logout: () => void;
    }
    
    const AuthContext = createContext<AuthContextType | undefined>(undefined);
    
    export const useAuth = () => {
      const context = useContext(AuthContext);
      if(context === undefined) {
        throw new Error("AuthProviderの中で宣言してください。")
      }
      return context;
    }
    
    // プロバイダーを作成する
    const AuthProvider = ({children}: {children :ReactNode}) => {
      const [user, setUser] = useState<User | null>(null);
    
      // ログイン・ログアウトを仮に実装する
      const login = (userInfo: User) => {
        if(userInfo.username === "testUser" && userInfo.email === "test@gmail.com") {
          setUser(userInfo);
        }
        else {
          console.log("ログインできません")
        }
      };
      const logout = () => {
        setUser(null)
      };
      // childrenはreact.nodeのこと
    
      const contextValue = {
        user,
        login,
        logout
      }
    
      return (
        <AuthContext.Provider value={contextValue}>
          {children}
        </AuthContext.Provider>
      )
    }
    
    export default AuthProvider;
    ```
    
    ### 説明
    
    1. **typescriptの定義**
        
        ```tsx
        type User = {
          id: string;
          username: string;
          email: string;
        }
        
        interface AuthContextType {
          user: User | null;
          login: (userInfo: User) => void;
          logout: () => void;
        }
        ```
        
        これは、TypeScriptでユーザー情報の型を定義していますの。
        
        `type User`はユーザー情報の型を定義しており、`AuthContextType`では、`AuthContext`の型を定義しています。

        このインターフェースは、提供する値とメソッドを指定しますわ。  
        <br>
    2. **createContext**
        
        ```tsx
        const AuthContext = createContext<AuthContextType | undefined>(undefined);
        ```
        
        コンテキストを作成しますの。
        
        `createContext<AuthContextType | undefined>(undefined)`という形で型を指定しています。
        <br>
    3. **カスタムフックのUseAuth()**
        
        ```tsx
        export const useAuth = () => {
          const context = useContext(AuthContext);
          if (context === undefined) {
            throw new Error("AuthProviderの中で宣言してください。");
          }
          return context;
        };
        ```
        
        `useContext(AuthContext)`を使用し、変数`context`にコンテキストの現在の値を取得しますわ。
        
        そして、`context`を返しますわ。
        
        また、`if`文で、`context`が`undefined`の場合に、エラーを返すようにしていますの。
        <br>
    4. **プロバイダーの作成**
        
        ```tsx
        const AuthProvider = ({children}: {children :ReactNode}) => {
        ```
        
        `children`は`AuthProvider`の子要素を指し、`{children :ReactNode}`という型注釈で、`children`がReactノードであることを示していますわ。
        
        ```tsx
        const [user, setUser] = useState<User | null>(null);
        ```
        
        `user`の状態を`useState`で管理しますわ。初期値は`null`ですの。
        
        ```tsx
        const login = (userInfo: User) => {
          if(userInfo.username === "testUser" && userInfo.email === "test@gmail.com") {
            setUser(userInfo);
          } else {
            console.log("ログインできません");
          }
        };
        
        const logout = () => {
          setUser(null);
        };
        
        ```
        
        `login`関数は、`userInfo`を受け取って、それを元に条件分岐で、`user`状態を更新するか決めていますわ。また、`logout`は`setUser`を`null`にするだけですわ。
        
        ```tsx
        const contextValue = {
          user,
          login,
          logout
        };
        ```
        
        `contextValue`はコンテキストプロバイダーが提供する値を準備していますの。
        
        ```tsx
        return (
          <AuthContext.Provider value={contextValue}>
            {children}
          </AuthContext.Provider>
        );
        
        ```
        
        最後に、`AuthContext.Provider`で`contextValue`をコンテキストの値として設定しますわ。
        
        `{children}`をレンダリングすることで、`AuthProvider`の子要素がこのコンテキストにアクセスできるようになりますの。
        
        ## Provider内の関数や値のメモ化
        
        **メモ化**とは、関数や値をメモリとして一時的に保存することですの。これにより、不要な再レンダリングを減らし、パフォーマンスの向上が期待できるのですわ。
        
        現在の状態では、ログインすると再レンダリングが発生し、ログイン関数の再生成や値の再計算が行われる可能性があり、パフォーマンスに影響を与えるかもしれませんの。ログインやログアウトの関数はメモ化しておくと良いですわ。
        
        ```tsx
          // ログイン・ログアウトを仮に実装する
          const login = useCallback((userInfo: User) => {
            if (
              userInfo.username === "testUser" &&
              userInfo.email === "test@gmail.com"
            ) {
              setUser(userInfo);
            } else {
              console.log("ログインできません");
            }
          }, []);
        
          const logout = useCallback(() => {
            setUser(null);
          }, []);
        
          const contextValue = useMemo(
            () => ({
              user,
              login,
              logout
            }),
            [user, login, logout]
          )
        ```
        
        1. **useCallbackによる関数のメモ化**
            
            `useCallback`フックを使用し、`login`と`logout`関数をメモ化していますの。これにより、依存配列が更新されない限り、これらの関数は同じインスタンスを保持し続け、際レンダリング時に再生成されませんの。
            <br>
            
        2. **useMemoによる値のメモ化**
            
            `useMemo`フックを使用して、`contextValue`をメモ化していますの。これにより、`user`、`login`、`logout`が変更されない限り、`contextValue`は再計算されませんの。
            
        
        ## まとめ
        
        <font color="red">**useContextの概要と利用法**</font>
        
        `useContext`フックはReactのコンポーネントでデータを深く受け渡す際に使用し、propsのバケツリレー問題を解決しますの。`Context`を使うことで、データを一元管理し、必要なコンポーネントで参照できるようにするのですわ。
        
        <font color="red">**主要なユースケース**</font>
        
        `useContext`は、ユーザー認証やテーマの切り替えなどに使われることが多いですわ。認証情報やテーマ状態を`Context`で管理し、アプリケーション全体で共通の状態を保持できますの。
        
        <font color="red">**Contextの作成とProviderの設定**</font>
        
        まず`Context`を作成し、それを提供する`Provider`を設定しますの。これにより、アプリケーション全体や特定のコンポーネントでデータを供給できるのですわ。`AuthProvider`は例として、ユーザー情報を管理するプロバイダーとして機能します。
        
        <font color="red">**ProviderでのラップとuseContextの利用**</font>
        
        作成した`Provider`でアプリケーション全体をラップし、その`Context`の値を利用するために`useContext`を使いますの。これにより、どのコンポーネントでも簡単に認証情報を取得し、操作できるようになるのですわ。
        
        <font color="red">**メモ化によるパフォーマンス向上**</font>
        
        `useCallback`と`useMemo`を使用して、関数や値をメモ化することで、不要な再レンダリングを防ぎ、パフォーマンスを向上させますの。これにより、`login`や`logout`の関数、及び`contextValue`が変更されない限り、同じインスタンスを保持し続けますの。