📌
【React Hooks】useContextの使い方
useContextとは
DOMツリー内で、親コンポーネントから子コンポーネントにデータを渡す場合、propsを通して、データを渡す。しかし、子コンポーネントより下の階層のコンポーネントにデータを渡したい場合、親コンポーネント→子コンポーネント→孫コンポーネント→...というようにpropsを渡す必要がある。(これをpropsのバケツリレーと呼ぶ。)
そこで、useContextAPIを使えば、子コンポーネントより下の階層のコンポーネントに直接データを渡すことができる。
使い方
基本
- まず、親コンポーネント内で
createContext
によりContextを作成する。ContextでラップしたいデータをThemeContext.Provider
を通して渡し、受け取り先のコンポーネントをProviderの小要素に記述する。 - 受け取り先のコンポーネント(上の例では孫、ひ孫コンポーネント)で
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コンポーネントの再レンダリングが行われなくなる。
参考文献
Discussion