概要
Contextはコンポーネントが親コンポーネントから情報を受け取る際に、それをpropsとして渡さずに済むようにします。
アプリのトップレベルのコンポーネントはネストの数に関係なくその下にある全てのコンポーネントに対してprops経由なしに情報を渡せる。
useContext
Contex
tを使用するためには、その値を使用したいツリー全体の親コンポーネントを対応するContext.Provider
でラップします。
以下の例ではButtonコンポーネント
内でtheme
としての値を使用したい場合に、Buttonコンポーネント
の親コンポーネントであるFormコンポーネント
をThemeContext.Provider
でラップする。
function MyPage() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}
function Form() {
return (
// ...
<Button />
// ...
)
}
そして、useContext
を使用することによってその値をとして "dark"を受け取ることができます。ここでButtonコンポーネント
がProviderとの間に何層ネストされてても関係なくuseContext
の値は取得することができる。
import { useContext } from 'react';
function Button() {
const theme = useContext(ThemeContext);
// ...
もしProvider
を親ツリーから見つけられない場合は、useContext
が返す値は、そのContext
を作成した時に指定したデフォルト値になる。
const ThemeContext = createContext(null);
useContextを使った例
codesandboxの例のように、contextを更新したい場合は、setStateをProvider配下のコンポーネントに渡してイベントハンドラー等で発火させてstateの値を更新する。 Providerの引数に value={{ theme, setTheme }}
のように渡してconst {setTheme} = useContext(ThemeContext)
のように受け取りstateを更新する関数を受け取ることもできる。
大規模アプリでのContext
ContextとReducer
大規模なアプリでは、contextとreducerを組み合わせて、ある状態に関連するロジックをコンポーネントから抽出することが多い。
こうすることによって、contextの更新に関連するロジックをコンポーネントから分離でき単体テスト等が書きやすくメンテナンスしやすいコードが書ける。
レンダリングの最適化
ログインの認証状態等を管理するために最上位の親コンポーネントでProviderでラップすることが多いが、その際にページ移動のような遷移だけで login関数が別オブジェクトとして認識されるために、再レンダリングが走りuseContext(AuthContext)
を利用する深い子コンポーネントまで再レンダリングしてしまう。
function MyApp() {
const [currentUser, setCurrentUser] = useState(null);
function login(response) {
storeCredentials(response.credentials);
setCurrentUser(response.user);
}
return (
<AuthContext.Provider value={{ currentUser, login }}>
<Page />
</AuthContext.Provider>
);
}
再レンダリングの必要はないため、別オブジェクトとして認識されないように useCallback
でlogin関数をラップして、オブジェクトの作成を useMemo
でvalueとして渡している {currentUser,login}
をuseMemo
でラップすることでパフォーマンスが向上できる。
こうすることによって、currentUser
が変更されない限り、useContext(AuthProvider)
を呼び出すコンポーネントは再レンダリングの必要がなくなる。
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>
);
}
その他の使い方
同じ名前のProviderでラップすることでcontextの値をOverrideすることができる。
<ThemeContext.Provider value="dark">
...
<ThemeContext.Provider value="light">
<Footer /> // ← この中での const theme = useContext(ThemeContext); のthemeの値は "light"
</ThemeContext.Provider>
...
</ThemeContext.Provider>
参考