0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

useContextの基礎を理解する【React-Hooks】

Last updated at Posted at 2024-07-08

React Hooks学習のアウトプット記事です。

useContextとは

  • stateをコンポーネント間で共有したい時に使用する
  • propsのバケツリレーをしなくても、値を子コンポーネントに渡すことができる

基本的な使い方

1. データを保持したいContextを作成

/src/Example.jsx
import { createContext } from "react";
import { Child } from "./components/Child";

// createContextを定義
export const ExampleContext = createContext("hello");

const Example = () => {
  return <Child />;
};

export default Example;

2. 子コンポーネントで呼び出す時

  • useContext と作成したContext(ExampleContext)をインポート
  • ExampleContextで設定した値を取得
/components/Child.jsx
import { useContext } from "react";
import { ExampleContext } from "../Example";

export const Child = () => {
  // ExampleContextで設定した値を取得する
  const value = useContext(ExampleContext);

  return (
    <p>{value}</p> // "hello"
  );
};

実際に使ってみる

ラジオボタンの切替で色が変わる仕組みをuseContext を使って作ってみる。

useContext.gif

1. 子コンポーネントで使うための準備

  • ThemeContextの作成
  • ThemeProvider を作成し、この中で持ち回りたい状態変数を宣言
    (これを親コンポーネントで呼び出し、子コンポーネントをラップする)
  • ThemeContextの呼び出し関数を作成
    (子コンポーネントごとに呼び出すこともできるが、ここでまとめておいた方が◎)
src/context/ThemeContext.tsx
import React, { createContext, useContext, useState } from "react";

// 型定義
type ThemeProviderProps = {
  children: React.ReactNode;
};

// ThemeContextを作成
export const ThemeContext = createContext<
  [string, React.Dispatch<React.SetStateAction<string>>] | undefined
>(undefined);

// 親コンポーネントで呼び出し、子コンポーネントにラップする
export const ThemeProvider = ({ children }: ThemeProviderProps) => {
  // テーマの状態管理
  const [theme, setTheme] = useState("light");

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

// ThemeContextの呼び出し(子コンポーネントで呼び出すときに使用)
export const useTheme = () => {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error("ThemeProviderの外で呼び出されました");
  }
  return context;
};

2. 親コンポーネントでの記述

  • ThemeContext.tsx で作成した、ThemeProvider 関数を呼び出し、子コンポーネントをラップする
src/App.js
import { Header } from "./components/Header";
import { Main } from "./components/Main";
import { ThemeProvider } from "./context/ThemeContext";
import "./styles.css";

export default function App() {
  return (
    <main className="App">
      <ThemeProvider>
        {/* ここにラップされたコンポーネントは、コンテキストが使える */}
        <Header />
      </ThemeProvider>
    </main>
  );
}

3. 子コンポーネントでの記述

  • ThemeContext.tsx で作成した、useTheme 関数を呼び出す
  • useTheme 関数から、使いたい変数を取り出す
src/components/Header.tsx
import React from "react";
import "../styles.css";
import { useTheme } from "../context/ThemeContext";

export const Header = () => {
  // 選択できるテーマ
  const THEMES: string[] = ["light", "dark", "red"];

  // ThemeContext.tsx で作成した、useTheme 関数を呼び出す
  const [theme, setTheme] = useTheme();
  // const [,setTheme] = useTheme(); // setTheme だけ取り出したい場合はこのように書ける

  // 選択変更で theme の状態を更新
  const changeTheme = (e) => setTheme(e.target.value);

  return (
    <header className={`content-${theme}`}>
      {THEMES.map((_theme) => (
        <label key={_theme}>
          <input
            type="radio"
            value={_theme}
            checked={theme === _theme}
            onChange={changeTheme}
          />
          {_theme}
        </label>
      ))}
    </header>
  );
};

注意点

  • ステートが更新されると、子コンポーネントは全て再レンダリングされる
    => 不必要な再レンダリングにより、パフォーマンスが下がる恐れ

対処法

コンテキストとプロバイダーを、「状態の値(state)」と「更新用関数(setState)」とで分割することで、
テーマとテーマの更新関数を分けて使用できるようになる

src/context/ThemeContext.tsx
import React, {
  createContext,
  Dispatch,
  SetStateAction,
  useContext,
  useState,
} from "react";

// 型定義
type ThemeProviderProps = {
  children: React.ReactNode;
};

// ThemeContextを作成
// stateの状態値
export const ThemeContext = createContext<string | undefined>(undefined);
// 更新用関数
export const UpdateThemeContext = createContext<
  Dispatch<SetStateAction<string>> | undefined
>(undefined);

// 親コンポーネントで呼び出し、子コンポーネントにラップする
export const ThemeProvider = ({ children }: ThemeProviderProps) => {
  // テーマの状態管理
  const [theme, setTheme] = useState("light");

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

// ThemeContextの呼び出し(子コンポーネントで呼び出すときに使用)
// stateの状態値の呼び出し
export const useTheme = () => {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error("ThemeProviderの外で呼び出されました");
  }
  return context;
};

// 更新用関数の呼び出し
export const useUpdateTheme = () => {
  const context = useContext(UpdateThemeContext);
  if (!context) {
    throw new Error("ThemeProviderの外で呼び出されました");
  }
  return context;
};

(そこまで規模の大きいアプリケーションを作ったことがないので実感はないが、
増えすぎるとこのファイルを読み解くのが難しくなりそうな…)


以前ユーザー情報を持ち回る処理を書いている時に使いました。
また使う前に、改めて理解したかったので、分からなくなくなったらこの記事を読み返します。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?