useContext
useContext
はコンポーネントから context を読み取り、サブスクライブするための React のフックです。
const value = useContext(SomeContext)
リファレンス
useContext(SomeContext)
コンポーネントのトップレベルで useContext
を呼び出して、コンテクスト を読み取り、サブスクライブします。
import { useContext } from 'react';
function MyComponent() {
const theme = useContext(ThemeContext);
// ...
引数
SomeContext
: 事前にcreateContext
で作成したコンテクストになります。コンテクスト自体は情報を保持せず、提供できる情報やコンポーネントから読み取れるような情報を表しているだけです。
返り値
useContext
は、呼び出したコンポーネントのコンテクスト値を返します。コンポーネントがツリー内で呼び出されるとき、その上位に位置する最も近い SomeContext.Provider
に渡された value
として決定されます。そのようなプロバイダが存在しない場合は、返り値はそのコンテクストの createContext
に渡した defaultValue
になります。その返り値は常に最新になります。React は、コンテクストを読み取ったコンポーネントが変更されると、自動的に再レンダーします。
注意点
- コンポーネントの
useContext()
呼び出しは、同じコンポーネントから返されるプロバイダの影響を受けません。該当する<Context.Provider>
は、useContext()
を呼び出したコンポーネントの上にある必要があります。 - 特定のコンテクストを使用する全ての子コンポーネントは、異なる
value
を受け取るプロバイダから始まり、React によって自動的に再レンダーします。前の値と次の値は、Object.is
で比較されます。memo
で再レンダーをスキップしても、子のプロバイダは新しいコンテクスト値を受け取ることはありません。 - ビルドシステムから生成されたアウトプットの中にモジュールの重複があったとき(シンボリックリンクで起こり得る場合がある)、コンテクストを壊す可能性があります。コンテクストを介して何かを渡すことは、コンテクストを提供するために使用する
SomeContext
と、読み込むために使用するSomeContext
が、===
比較によって決定されるように、正確に同じオブジェクトである場合にのみ動作します。
使い方
ツリーの深くにデータを渡す
コンポーネントのトップレベルで useContext
を呼び出して コンテクスト を読み取り、サブスクライブします。
import { useContext } from 'react';
function Button() {
const theme = useContext(ThemeContext);
// ...
useContext
は コンテクストの値 を 渡したコンテクスト のために返します。コンテクストの値を決定するために、React はコンポーネントツリーを探索し、特定のコンテクストに対して最も近い上位のコンテクストプロバイダを見つけます。
コンテクストを Button
に渡すために、該当のコンテクストプロバイダでラップします :
function MyPage() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}
function Form() {
// ... renders buttons inside ...
}
プロバイダと Button
の間にどれだけ多くのコンポーネントの層があっても関係ありません。Form
の内部のどこかで Button
が useContext(ThemeContext)
を呼び出すとき、値として"dark"
を受け取ります。
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. と組み合わせてください。親コンポーネントで state 変数を宣言します。親コンポーネントで state 変数を宣言して、現在の state を コンテクストの値 としてプロバイダに渡します。
function MyPage() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<Form />
<Button onClick={() => {
setTheme('light');
}}>
Switch to light theme
</Button>
</ThemeContext.Provider>
);
}
これにより、プロバイダの内部になるどの Button
も現在の theme
値を受け取るようになります。もし setTheme を呼び出してプロバイダに渡す theme 値を更新すると、すべての Button
コンポーネントは新たな 'light'
値で再レンダーされます。
例 1/5: コンテクストを介して値を更新する
この例では、MyApp
コンポーネントが state 変数を保持し、それが ThemeContext
プロバイダに渡されます。“ダークモード”のチェックボックスを選択すると、ステートが更新されます。提供された値を変更すると、そのコンテクストを使用しているすべてのコンポーネントが再レンダーされます。
import { createContext, useContext, useState } from 'react'; const ThemeContext = createContext(null); export default function MyApp() { const [theme, setTheme] = useState('light'); return ( <ThemeContext.Provider value={theme}> <Form /> <label> <input type="checkbox" checked={theme === 'dark'} onChange={(e) => { setTheme(e.target.checked ? 'dark' : 'light') }} /> Use dark mode </label> </ThemeContext.Provider> ) } function Form({ children }) { 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> ); }
value="dark"
は "dark"
という文字列を渡しますが、value={theme}
は JavaScript の theme
変数の値を JSX の中括弧 で渡しすことに注意してください。中括弧を使うことで、文字列以外のコンテクスト値も渡すことができます。
フォールバックのデフォルト値の指定
React が親ツリー中に特定のコンテクストのプロバイダを見つけることができない場合、useContext()
が返すコンテクストの値は、そのコンテキストを作成したときに指定したデフォルト値と等しくなります:
const ThemeContext = createContext(null);
デフォルト値は決して変わりません。コンテクストを更新するには、上記で説明したように、状態と共に使用します。
よくあることですが、null
の代わりに、デフォルトとして使用できるより意味のある値があります。例えば:
const ThemeContext = createContext('light');
このようにすると、もし間違って対応するプロバイダなしで何かのコンポーネントをレンダーしてしまっても、それが壊れることはありません。テスト環境で多くのプロバイダをテストに設定しなくても、コンポーネントがうまく動作するのに役立ちます。
下記の例では、「テーマの切り替え」ボタンは常に明るい色になります。なぜなら、それはテーマコンテクストプロバイダの外側にあるためであり、デフォルトのコンテクストテーマ値は 'light'
だからです。デフォルトのテーマを 'dark'
に編集してみてください。
import { createContext, useContext, useState } from 'react'; const ThemeContext = createContext('light'); export default function MyApp() { const [theme, setTheme] = useState('light'); return ( <> <ThemeContext.Provider value={theme}> <Form /> </ThemeContext.Provider> <Button onClick={() => { setTheme(theme === 'dark' ? 'light' : 'dark'); }}> Toggle theme </Button> </> ) } function Form({ children }) { 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, onClick }) { const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( <button className={className} onClick={onClick}> {children} </button> ); }
ツリーの一部のコンテクストを上書きする
ツリーの一部を異なる値のプロバイダでラップすることにより、その部分のコンテクストを上書きすることができます。
<ThemeContext.Provider value="dark">
...
<ThemeContext.Provider value="light">
<Footer />
</ThemeContext.Provider>
...
</ThemeContext.Provider>
必要なだけプロバイダをネストして上書きすることができます。
例 1/2: テーマの上書き
ここでは、Footer
内部のボタンは外部のボタン("dark"
)とは異なるコンテクスト値("light"
)を受け取ります。
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> <ThemeContext.Provider value="light"> <Footer /> </ThemeContext.Provider> </Panel> ); } function Footer() { return ( <footer> <Button>Settings</Button> </footer> ); } function Panel({ title, children }) { const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( <section className={className}> {title && <h1>{title}</h1>} {children} </section> ) } function Button({ children }) { const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( <button className={className}> {children} </button> ); }
オブジェクトと関数を渡すときの再レンダーの最適化
あなたはコンテクストを介して任意の値を渡すことができます、オブジェクトや関数を含みます。
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>
);
}
ここでは、context value は 2 つのプロパティを持つ JavaScript オブジェクトで、そのうちの 1 つは関数です。MyApp
が再レンダーされるたびに(例えば、ルートの更新時など)、これは異なるオブジェクトを指し、異なる関数を指しますので、useContext(AuthContext)
を呼び出すツリー内のすべてのコンポーネントも再レンダーしなければなりません。
小規模なアプリケーションでは、これは問題ではありません。しかし、基礎となるデータ、例えば currentUser
が変更されていない場合、それらを再レンダーする必要はありません。その事実を活用するために、React がそれを活用できるように、login
関数を useCallback
でラップし、オブジェクトの作成を useMemo
でラップすることができます。これはパフォーマンスの最適化です:
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>
);
}
この変更の結果、MyApp
が再レンダーする必要があっても、currentUser
が変更されていない限り、useContext(AuthContext)
を呼び出すコンポーネントを再レンダーする必要はありません。
useMemo
と useCallback
についてもっと読むことができます。
トラブルシューティング
マイコンポーネントはプロバイダからの値を見ることができません
これが起こる一般的な方法はいくつかあります:
useContext()
を呼び出している同じコンポーネント(またはそれ以下)で<SomeContext.Provider>
をレンダーしています。useContext()
を呼び出すコンポーネントの上と外側に<SomeContext.Provider>
を移動してください。<SomeContext.Provider>
でコンポーネントをラップするのを忘れているかもしれません、または思っていたよりもツリーの異なる部分に置いているかもしれません。React DevTools を使用して階層が正しいか確認してください。SomeContext
がプロバイダコンポーネントから見たものとSomeContext
が読み取りコンポーネントから見たものとで 2 つの異なるオブジェクトになるようなツールでのビルド問題に遭遇している可能性があります。これは、たとえば、シンボリックリンクを使用している場合に発生することがあります。window.SomeContext1
およびwindow.SomeContext2
にそれらをグローバルに割り当てて、コンソールでwindow.SomeContext1 === window.SomeContext2
がどうか確認することでこれを確認できます。それらが同じでない場合は、ビルドツールレベルでその問題を修正してください。
デフォルト値が異なるにもかかわらず、私のコンテクストから常に undefined
を取得しています
ツリー内に value
なしのプロバイダがある可能性があります:
// 🚩 Doesn't work: no value prop
<ThemeContext.Provider>
<Button />
</ThemeContext.Provider>
value
を指定するのを忘れると、value={undefined}
を渡すのと同じです。
また、誤って別のプロップ名を使用した可能性もあります:
// 🚩 Doesn't work: prop should be called "value"
<ThemeContext.Provider theme={theme}>
<Button />
</ThemeContext.Provider>
これらのどちらの場合も、React からコンソールに警告が表示されるはずです。それらを修正するには、プロパティを value と呼びます:
// ✅ Passing the value prop
<ThemeContext.Provider value={theme}>
<Button />
</ThemeContext.Provider>
あなたが createContext(defaultValue)呼び出しからのデフォルト値 は、全く一致するプロバイダが存在しない場合にのみ使用されます。もし親ツリーのどこかに <SomeContext.Provider value={undefined}>
コンポーネントがある場合、useContext(SomeContext)
を呼び出すコンポーネントはコンテクスト値として undefined
を受け取ります。