📌

【React Hooks】useContextの使い方

2024/08/17に公開

useContextとは

DOMツリー内で、親コンポーネントから子コンポーネントにデータを渡す場合、propsを通して、データを渡す。しかし、子コンポーネントより下の階層のコンポーネントにデータを渡したい場合、親コンポーネント→子コンポーネント→孫コンポーネント→...というようにpropsを渡す必要がある。(これをpropsのバケツリレーと呼ぶ。)

そこで、useContextAPIを使えば、子コンポーネントより下の階層のコンポーネントに直接データを渡すことができる。

使い方

基本

  1. まず、親コンポーネント内でcreateContextによりContextを作成する。ContextでラップしたいデータをThemeContext.Providerを通して渡し、受け取り先のコンポーネントをProviderの小要素に記述する。
  2. 受け取り先のコンポーネント(上の例では孫、ひ孫コンポーネント)でuseContextによりContextを取得する。
App.js
import { createContext, useContext } from 'react';

const ThemeContext = createContext(null);

//親コンポーネント
export default function MyApp() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  )
}

//子コンポーネント
function Form() {
  return (
    <Panel title="Welcome">
      <Button>Sign up</Button>
      <Button>Log in</Button>
    </Panel>
  );
}

//孫コンポーネント
function Panel({ title, children }) {
  const theme = useContext(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

//ひ孫コンポーネント
function Button({ children }) {
  const theme = useContext(ThemeContext);
  const className = 'button-' + theme;
  return (
    <button className={className}>
      {children}
    </button>
  );
}

stateの値をContextで渡す

useStateと組みわせて使う方法。Providerを通じて{ currentUser, setCurrentUser }のようなオブジェクトの形で
stateとその更新関数を渡して、ひ孫コンポーネント(LoginButton)内で分割代入により取得する。

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

const CurrentUserContext = createContext(null);

//親
export default function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);
  return (
    <CurrentUserContext.Provider
      value={{
        currentUser,
        setCurrentUser
      }}
    >
      <Form />
    </CurrentUserContext.Provider>
  );
}

//子
function Form({ children }) {
  return (
    <Panel title="Welcome">
      <LoginButton />
    </Panel>
  );
}

//ひ孫
function LoginButton() {
  const {
    currentUser,
    setCurrentUser
  } = useContext(CurrentUserContext);

  if (currentUser !== null) {
    return <p>You logged in as {currentUser.name}.</p>;
  }

  return (
    <Button onClick={() => {
      setCurrentUser({ name: 'Advika' })
    }}>Log in as Advika</Button>
  );
}

アプリケーションが大きくなった場合は、CurrentUserContext.ProviderをCurrentUserContext.jsxに切り出すことも検討する。

Contextの値を上書きする

Provider内部にProviderを記述することによりContextの値を上書きすることができる。

<ThemeContext.Provider value="dark">
  ...
  <ThemeContext.Provider value="light">
    <Footer />
  </ThemeContext.Provider>
  ...
</ThemeContext.Provider>

useMemo,useCallbackを用いた再レンダリングの防止(パフォーマンス最適化)

親コンポーネントが再レンダリングされた時、定義されたContextも再レンダリングの対象となるため、下階層でuseContextを呼び出している全てのコンポーネントが再レンダリングされてしまう。

import { useCallback, useMemo } from 'react';

function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);

  const login = useCallback((response) => {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }, []);

  const contextValue = useMemo(() => ({
    currentUser,
    login
  }), [currentUser, login]);

  return (
    <AuthContext.Provider value={contextValue}>
      <Page />
    </AuthContext.Provider>
  );
}

これによりcurrentUserが変更されない限り、Pageコンポーネントの再レンダリングが行われなくなる。

参考文献

https://ja.react.dev/reference/react/useContext#usage

Discussion