useContext
是一個 React Hook,可讓您從元件中讀取並訂閱 Context。
const value = useContext(SomeContext)
參考
useContext(SomeContext)
在元件的頂層呼叫 useContext
來讀取並訂閱 Context。
import { useContext } from 'react';
function MyComponent() {
const theme = useContext(ThemeContext);
// ...
參數
SomeContext
:您先前使用createContext
建立的 Context。Context 本身不包含資訊,它只代表您可以提供或從元件讀取的資訊種類。
回傳值
useContext
會傳回呼叫元件的 Context 值。它被決定為傳遞給樹狀結構中呼叫元件上方最接近的 SomeContext.Provider
的 value
。如果沒有這樣的 Provider,則傳回值將會是您傳遞給 createContext
作為該 Context 的 defaultValue
。傳回值始終保持最新狀態。如果 Context 變更,React 會自動重新渲染讀取 Context 的元件。
注意事項
- 在元件中呼叫
useContext()
並不會受到**相同**元件所返回的提供者影響。相對應的<Context.Provider>
**需要位於**執行useContext()
呼叫的元件**之上**。 - 當 provider 收到不同的
value
時,React 會**自動重新渲染**所有使用特定 context 的子元件。先前和下一個值會使用Object.is
比較進行比較。使用memo
跳過重新渲染並不會阻止子元件接收新的 context 值。 - 如果您的建置系統在輸出中產生重複的模組(使用符號連結時可能會發生這種情況),這可能會破壞 context。透過 context 傳遞某些內容只有在您用於提供 context 的
SomeContext
和您用於讀取它的SomeContext
**完全是同一個物件**時才有效,這由===
比較決定。
用法
將資料深入傳遞到樹狀結構中
在元件的頂層呼叫 useContext
來讀取並訂閱 Context。
import { useContext } from 'react';
function Button() {
const theme = useContext(ThemeContext);
// ...
useContext
會返回您傳遞的 context 的 context 值。為了確定 context 值,React 會搜尋元件樹,並找到**上方最接近的 context provider**,適用於該特定 context。
要將 context 傳遞給 Button
,請將它或它的其中一個父元件包裝到相對應的 context provider 中。
function MyPage() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}
function Form() {
// ... renders buttons inside ...
}
在 provider 和 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> ); }
更新透過 context 傳遞的資料
通常,您會希望 context 隨著時間而改變。要更新 context,請將其與 狀態 結合。在父元件中宣告一個狀態變數,並將目前狀態作為 context 值 傳遞給 provider。
function MyPage() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<Form />
<Button onClick={() => {
setTheme('light');
}}>
Switch to light theme
</Button>
</ThemeContext.Provider>
);
}
現在,provider 內的任何 Button
都將會收到目前的 theme
值。如果您呼叫 setTheme
來更新您傳遞給 provider 的 theme
值,所有 Button
元件都將使用新的 'light'
值重新渲染。
範例 1說明 5: 透過 context 更新值
在此範例中,MyApp
元件持有一個狀態變數,然後將其傳遞給 ThemeContext
provider。勾選「深色模式」核取方塊會更新狀態。更改提供的值會重新渲染所有使用該 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}
會使用 JSX 大括號 傳遞 JavaScript theme
變數的值。大括號也允許您傳遞非字串的 context 值。
指定預設的後備值
如果 React 在父元件樹中找不到特定 Context 的提供者,則 useContext()
返回的 Context 值將會等於您在 建立該 Context 時指定的 預設值。
const ThemeContext = createContext(null);
預設值永遠不會改變。如果您想要更新 Context,請搭配狀態使用它,如上文所述。
通常,您可以使用一些更有意義的值作為預設值,而不是 null
,例如:
const ThemeContext = createContext('light');
這樣,如果您不小心渲染了沒有對應提供者的元件,它也不會損壞。這也有助於您的元件在測試環境中良好運作,而無需在測試中設置大量的提供者。
在下面的範例中,「切換主題」按鈕始終是亮色的,因為它位於任何主題 Context 提供者之外,而且預設的 Context 主題值是 '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> ); }
覆蓋部分元件樹的 Context
您可以透過將部分元件樹用具有不同值的提供者包覆,來覆蓋該部分的 Context。
<ThemeContext.Provider value="dark">
...
<ThemeContext.Provider value="light">
<Footer />
</ThemeContext.Provider>
...
</ThemeContext.Provider>
您可以根據需要嵌套和覆蓋任意數量的提供者。
範例 1說明 2: 覆蓋主題
在這裡,Footer
*內部*的按鈕接收到的 Context 值 ("light"
) 與外部的按鈕 ("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> <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> ); }
傳遞物件和函式時優化重新渲染
您可以透過 Context 傳遞任何值,包括物件和函式。
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 值 是一個具有兩個屬性的 JavaScript 物件,其中一個屬性是一個函式。每當 MyApp
重新渲染時(例如,在路由更新時),這將是一個指向*不同*函式的*不同*物件,因此 React 也必須重新渲染元件樹中所有呼叫 useContext(AuthContext)
的元件。
在較小的應用程式中,這不是問題。但是,如果基礎資料(例如 currentUser
)沒有改變,則無需重新渲染它們。為了幫助 React 利用這個事實,您可以使用 useCallback
包覆 login
函式,並使用 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
需要重新渲染,呼叫 useContext(AuthContext)
的元件也不需要重新渲染,除非 currentUser
發生變化。
閱讀更多關於 useMemo
和 useCallback
的資訊。
疑難排解
我的元件看不到來自我的 Provider 的值
有幾種常見的情況會導致這種情況發生
- 您在與呼叫
useContext()
的相同元件(或下方)中渲染了<SomeContext.Provider>
。請將<SomeContext.Provider>
移到呼叫useContext()
的元件*上方和外部*。 - 您可能忘記使用
<SomeContext.Provider>
包裹您的元件,或者您可能將它放在了樹中與您想像不同的部分。使用 React 開發者工具檢查階層是否正確。 - 您可能遇到了一些工具的建置問題,導致提供元件看到的
SomeContext
和讀取元件看到的SomeContext
是兩個不同的物件。例如,如果您使用符號連結,就會發生這種情況。您可以通過將它們賦值給全域變數(例如window.SomeContext1
和window.SomeContext2
),然後在控制台中檢查window.SomeContext1 === window.SomeContext2
是否成立來驗證這一點。如果它們不相同,請在建置工具層級修復該問題。
儘管預設值不同,但我總是從我的 Context 中得到 undefined
您可能在樹狀結構中有一個沒有 value
的 provider
// 🚩 Doesn't work: no value prop
<ThemeContext.Provider>
<Button />
</ThemeContext.Provider>
如果您忘記指定 value
,就像傳遞 value={undefined}
一樣。
您可能也錯誤地使用了不同的 prop 名稱
// 🚩 Doesn't work: prop should be called "value"
<ThemeContext.Provider theme={theme}>
<Button />
</ThemeContext.Provider>
在這兩種情況下,您都應該在控制台中看到來自 React 的警告。要修復它們,請呼叫 prop value
// ✅ Passing the value prop
<ThemeContext.Provider value={theme}>
<Button />
</ThemeContext.Provider>
請注意,您的 createContext(defaultValue)
呼叫中的預設值 僅在**上方完全沒有匹配的 provider 時**使用。如果父樹狀結構中的某處存在 <SomeContext.Provider value={undefined}>
元件,則呼叫 useContext(SomeContext)
的元件*將會*收到 undefined
作為 context 值。