useContext

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.Providervalue。如果沒有這樣的 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 會返回您傳遞的 contextcontext 值。為了確定 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" 作為值。

陷阱

useContext() 總是尋找呼叫它的元件**上方**最接近的 provider。它會向上搜尋,並且**不會**考慮您呼叫 useContext() 的元件中的 provider。

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' 值重新渲染。

更新 context 的範例

範例 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>

您可以根據需要嵌套和覆蓋任意數量的提供者。

覆蓋 Context 的範例

範例 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 發生變化。

閱讀更多關於 useMemouseCallback 的資訊。


疑難排解

我的元件看不到來自我的 Provider 的值

有幾種常見的情況會導致這種情況發生

  1. 您在與呼叫 useContext() 的相同元件(或下方)中渲染了 <SomeContext.Provider>。請將 <SomeContext.Provider> 移到呼叫 useContext() 的元件*上方和外部*。
  2. 您可能忘記使用 <SomeContext.Provider> 包裹您的元件,或者您可能將它放在了樹中與您想像不同的部分。使用 React 開發者工具檢查階層是否正確。
  3. 您可能遇到了一些工具的建置問題,導致提供元件看到的 SomeContext 和讀取元件看到的 SomeContext 是兩個不同的物件。例如,如果您使用符號連結,就會發生這種情況。您可以通過將它們賦值給全域變數(例如 window.SomeContext1window.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 值。