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
を使って作ってみる。
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;
};
(そこまで規模の大きいアプリケーションを作ったことがないので実感はないが、
増えすぎるとこのファイルを読み解くのが難しくなりそうな…)
以前ユーザー情報を持ち回る処理を書いている時に使いました。
また使う前に、改めて理解したかったので、分からなくなくなったらこの記事を読み返します。