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()
呼び出しを行うコンポーネントの 上にある必要があります。 - React は自動的に再レンダー 異なる
value
を受け取ったプロバイダから、特定のコンテクストを使用する子のプロバイダを開始します。前の値と次の値は、Object.is
で比較されます。memo
を使用して再レンダーをスキップしても、子のプロバイダが新しいコンテクスト値を受け取ることはありません。 - ビルドシステムから生成された結果の中にモジュールの重複があった場合(シンボリックリンクで起こり得る場合がある)、コンテクストを壊す可能性があります。コンテクストを介して何かを渡すことは、コンテクストを提供するために使用する
SomeContext
と、読み込むために使用するSomeContext
が、===
比較によって決定されるように、正確に同じオブジェクトである場合にのみ動作します。
使い方
ツリーの深い部分にデータを渡す
コンポーネントのトップレベルで useContext
を呼び出して コンテクスト を読み取り、サブスクライブします。
import { useContext } from 'react';
function Button() {
const theme = useContext(ThemeContext);
// ...
useContext
は context の値 を 渡した context のために返します。context の値を決定するために、React はコンポーネントツリーを探索し、特定の context について最も近い上位の context プロバイダを見つけます。
context を Button
に渡すために、それを対応する context プロバイダでラップします :
function MyPage() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}
function Form() {
// ... ボタンを内側にレンダーします ...
}
プロバイダと 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 を context value としてプロバイダに渡します。
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
値を受け取ります。プロバイダに渡す theme
値を更新するために setTheme
を呼び出すと、すべての Button
コンポーネントが新しい 'light'
値で再レンダーされます。
例 1/5: contextを介して値を更新する
この例では、MyApp
コンポーネントが state
変数を保持し、それを ThemeContext
プロバイダに渡します。"Dark mode"
のチェックボックスをチェックすると、state が更新されます。提供される値を変更すると、context を使用しているすべてのコンポーネントが再レンダーされます。
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 curly braces. で渡します。波括弧を使うと、文字列でないコンテクスト値も渡すことができます。
フォールバックのデフォルト値の指定
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
なしのプロバイダがある可能性があります:
// 🚩 動作しません:値プロパティがないからです
<ThemeContext.Provider>
<Button />
</ThemeContext.Provider>
value
を指定するのを忘れると、value={undefined}
を渡すのと同じです。
また、誤って別のプロップ名を使用した可能性もあります:
// 🚩 動作しません:プロパティは"value"と呼ばれるべきです
<ThemeContext.Provider theme={theme}>
<Button />
</ThemeContext.Provider>
これらのどちらの場合も、React からコンソールに警告が表示されるはずです。それらを修正するには、プロパティを value と呼びます:
// ✅ valueプロパティを渡す
<ThemeContext.Provider value={theme}>
<Button />
</ThemeContext.Provider>
あなたが createContext(defaultValue)呼び出しからのデフォルト値 は、全く一致するプロバイダが存在しない場合にのみ使用されます。もし親ツリーのどこかに <SomeContext.Provider value={undefined}>
コンポーネントがある場合、useContext(SomeContext)
を呼び出すコンポーネントはコンテクスト値として undefined
を受け取ります。