- 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