useContext

useContext 是一个 React Hook,它允许您从组件中读取和订阅 上下文

const value = useContext(SomeContext)

参考

useContext(SomeContext)

在组件的顶层调用 useContext 以读取和订阅 上下文

import { useContext } from 'react';

function MyComponent() {
const theme = useContext(ThemeContext);
// ...

请参阅下面的更多示例。

参数

  • SomeContext:您之前使用 createContext 创建的上下文。上下文本身不保存信息,它只代表您可以提供或从组件中读取的信息类型。

返回值

useContext 返回调用组件的上下文值。它被确定为传递给树中调用组件上方最近的 SomeContext.Providervalue。如果没有这样的提供程序,则返回的值将是您传递给 createContext 的该上下文的 defaultValue。返回的值始终是最新的。如果某些上下文发生更改,React 会自动重新渲染读取该上下文的组件。

注意事项

  • 组件中的 useContext() 调用不受相同组件返回的 provider 的影响。相应的 <Context.Provider> 需要位于执行 useContext() 调用的组件的上方
  • React 会从接收到不同 value 的 provider 开始,自动重新渲染所有使用特定 context 的子组件。先前和下一个值将使用 Object.is 比较进行比较。使用 memo 跳过重新渲染并不会阻止子组件接收新的 context 值。
  • 如果你的构建系统在输出中产生了重复的模块(这可能发生在使用符号链接时),则可能会破坏 context。仅当用于提供 context 的 SomeContext 和用于读取 context 的 SomeContext 完全是同一个对象时(通过 === 比较确定),才能通过 context 传递内容。

用法

将数据深度传递到树中

在组件的顶层调用 useContext 以读取和订阅 上下文

import { useContext } from 'react';

function Button() {
const theme = useContext(ThemeContext);
// ...

useContext 返回你传递的 contextcontext 值。为了确定 context 值,React 会搜索组件树并找到上方最近的该特定 context 的 context provider。

要将 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 的示例

通过 1context 5:
更新值的示例

在本例中,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 在父组件树中找不到该特定 上下文 的任何提供程序,则 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>

您可以根据需要嵌套和覆盖提供程序任意次数。

覆盖上下文的示例

通过 1context 2:
覆盖主题

在这里,Footer内部的按钮接收到的上下文值("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>
  );
}


传递对象和函数时优化重新渲染

您可以通过上下文传递任何值,包括对象和函数。

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>
);
}

这里,上下文值 是一个具有两个属性的 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 的更多信息。


故障排除

我的组件看不到来自我的提供程序的值

有几种常见的方式会导致这种情况发生

  1. 您正在同一个组件(或其下方)中渲染 <SomeContext.Provider> 和调用 useContext() 的位置。请将 <SomeContext.Provider> 移动到调用 useContext() 的组件的上方和外部
  2. 您可能忘记使用 <SomeContext.Provider> 包裹您的组件,或者您可能将其放置在组件树中与您预想不同的位置。请使用 React 开发者工具 检查层级结构是否正确。
  3. 您可能遇到了工具链中的一些构建问题,导致提供组件看到的 SomeContext 和读取组件看到的 SomeContext 是两个不同的对象。例如,如果您使用符号链接,就可能会发生这种情况。您可以通过将它们分配给全局变量(例如 window.SomeContext1window.SomeContext2)并在控制台中检查 window.SomeContext1 === window.SomeContext2 是否成立来验证这一点。如果它们不相同,请在构建工具级别修复该问题。

尽管默认值不同,但我从上下文中始终得到 undefined

您可能在树中有一个没有 value 的提供者

// 🚩 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) 调用中指定的默认值 仅在上方根本没有匹配的提供者时才会使用。如果父组件树中的某个位置存在 <SomeContext.Provider value={undefined}> 组件,则调用 useContext(SomeContext) 的组件收到 undefined 作为上下文值。