React Contextを使って下位コンポーネントに情報を渡す
- JavaScript
-
2024-08-02 - 更新:2024-08-04
通常、親コンポーネントから子コンポーネントにはpropsを使って情報を渡します。
しかし、深くネストされた下位コンポーネントに渡すには、メンテナンスのことも考えると非常に不便です。
コンテクスト(Context)を利用することで情報の受け渡しが簡単になります。
まずは基本の使い方から。
createContextでコンテクストオブジェクトを作成します。
createContextの引数はデフォルト値です。
import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
コンポーネントのトップレベルでuseContextを呼び出して、コンテクストを読み取ります。
function Button({ children }) {
const theme = useContext(ThemeContext);
const className = 'button-' + theme;
return (
<button className={className}>
{children}
</button>
);
}
コンテクストを上記のButtonに渡すには、該当のボタンあるいはその親コンポーネントのいずれかを、対応するコンテクストプロバイダでラップします。
export default function MyApp() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
)
}
function Form() {
// ... renders buttons inside ...
}
これでプロバイダとButtonの間にどれだけ多くのコンポーネントが挟まっていても関係なく情報を受け渡すことができます。
コンテクスト経由で渡されたデータの更新
コンテクストの情報を更新するにはuseStateフックを使って更新します。
function MyPage() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<Form />
<Button onClick={() => {
setTheme('light');
}}>
Switch to light theme
</Button>
</ThemeContext.Provider>
);
}
コンテクスト用のコンポーネントを作成
コンテクストをより汎用的に使うためにコンテクスト用のコンポーネントを作成します。
CountContext.jsx
import { createContext, useState, useContext } from 'react';
const CountContext = createContext();
export function useCountContext() {
return useContext(CountContext);
}
export function CountProvider({ children }) {
const [count, setCount] = useState(100);
const value = {
count,
setCount,
};
return (
<CountContext.Provider value={value}>{children}</CountContext.Provider>
);
}
CountContextコンポーネントをインポートして使ってみましょう。
import { CountProvider, useCountContext } from './CountContext';
function App() {
return (
<React.Fragment>
<CountProvider>
<Form />
</CountProvider>
</React.Fragment>
)
}
function Form() {
// ... renders buttons inside ...
}
function MyButton() {
const { count, setCount } = useCountContext();
return (
<Button onClick={() => {
setCount(count + 1);
}}>
add count
</Button>
);
}
これで他のコンポートネントから使いやすくなると思います。
コンテクストを介してオブジェクトを更新する
state変数と、それを更新するset関数も含めて下位コンポーネントに渡します。
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>
);
}
function Panel({ title, children }) {
return (
<section className="panel">
<h1>{title}</h1>
{children}
</section>
)
}
function Button({ children, onClick }) {
return (
<button className="button" onClick={onClick}>
{children}
</button>
);
}
オブジェクトや関数を渡すときの再レンダーの最適化
先ほどの方法だとMyAppが再レンダーされるたびに、useContext(CurrentUserContext)を呼び出しているすべてのコンポーネントも再レンダーされてしまいます。
小規模なアプリでは問題になりませんが、currentUserのような内部のデータが変更されていないなら、再レンダーする必要はありません。
データが変わっていないという事実をReactが最大限に活用できるように、set関数をuseCallbackでラップし、オブジェクトの生成をuseMemoでラップすることでこの問題を解決できます。
function MyApp() {
const [currentUser, setCurrentUser] = useState(null);
const login = useCallback((response) => {
setCurrentUser(response);
}, []);
const contextValue = useMemo(() => ({
currentUser,
login
}), [currentUser, login]);
return (
<CurrentUserContext.Provider value={contextValue}>
<Page />
</CurrentUserContext.Provider>
);
}
下位コンポーネントで利用するには以下のようにします。
function Page() {
const us = React.useContext(AuthContext);
// ...
// us.login({name: '...'});
// us.currentUser.name,
// ...
}
もしくは分割代入で使いやすくします。
function Page() {
const {currentUser, login} = React.useContext(AuthContext);
// ...
// login({name: '...'});
// currentUser.name,
// ...
}
参考URL:
React::Reference createContext
React::Reference useContext
React::Reference useCallback
React::Reference useMemo




