useContext 是一个 React 钩子,可让你从组件中读取和订阅 上下文

¥useContext is a React Hook that lets you read and subscribe to context from your component.

const value = useContext(SomeContext)

参考

¥Reference

useContext(SomeContext)

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

¥Call useContext at the top level of your component to read and subscribe to context.

import { useContext } from 'react';

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

请参阅下面的更多示例。

¥See more examples below.

参数

¥Parameters

  • SomeContext:你之前使用 createContext 创建的上下文。上下文本身不包含信息,它仅表示你可以提供或从组件中读取的信息类型。

    ¥SomeContext: The context that you’ve previously created with createContext. The context itself does not hold the information, it only represents the kind of information you can provide or read from components.

返回

¥Returns

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

¥useContext returns the context value for the calling component. It is determined as the value passed to the closest SomeContext.Provider above the calling component in the tree. If there is no such provider, then the returned value will be the defaultValue you have passed to createContext for that context. The returned value is always up-to-date. React automatically re-renders components that read some context if it changes.

注意事项

¥Caveats

  • 组件中的 useContext() 调用不受从同一组件返回的提供器的影响。相应的 <Context.Provider> 需要位于执行 useContext() 调用的组件之上。

    ¥useContext() call in a component is not affected by providers returned from the same component. The corresponding <Context.Provider> needs to be above the component doing the useContext() call.

  • React 会自动重新渲染所有使用特定上下文的子级,从接收到不同 value 的提供者开始。上一个和下一个值与 Object.is 比较进行比较。使用 memo 跳过重新渲染不会阻止子级接收新的上下文值。

    ¥React automatically re-renders all the children that use a particular context starting from the provider that receives a different value. The previous and the next values are compared with the Object.is comparison. Skipping re-renders with memo does not prevent the children receiving fresh context values.

  • 如果你的构建系统在输出中生成重复模块(符号链接可能会发生),这可能会破坏上下文。仅当用于提供上下文的 SomeContext 和用于读取上下文的 SomeContext 是完全相同的对象(由 === 比较确定)时,通过上下文传递某些内容才有效。

    ¥If your build system produces duplicates modules in the output (which can happen with symlinks), this can break context. Passing something via context only works if SomeContext that you use to provide context and SomeContext that you use to read it are exactly the same object, as determined by a === comparison.


用法

¥Usage

将数据深入传递到树中

¥Passing data deeply into the tree

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

¥Call useContext at the top level of your component to read and subscribe to context.

import { useContext } from 'react';

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

useContext 为你传递的 上下文 返回的 上下文值。为了确定上下文值,React 搜索组件树并找到上面与该特定上下文最接近的上下文提供者。

¥useContext returns the context value for the context you passed. To determine the context value, React searches the component tree and finds the closest context provider above for that particular context.

要将上下文传递给 Button,请将其或其父组件之一封装到相应的上下文提供器中:

¥To pass context to a Button, wrap it or one of its parent components into the corresponding context provider:

function MyPage() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}

function Form() {
// ... renders buttons inside ...
}

提供器和 Button 之间有多少层组件并不重要。当 Form 内部任何位置的 Button 调用 useContext(ThemeContext) 时,它将接收 "dark" 作为值。

¥It doesn’t matter how many layers of components there are between the provider and the Button. When a Button anywhere inside of Form calls useContext(ThemeContext), it will receive "dark" as the value.

易犯错误

useContext() 总是在调用它的组件之上寻找最近的提供器。它向上搜索,并且不考虑你调用 useContext() 的组件中的提供程序。

¥useContext() always looks for the closest provider above the component that calls it. It searches upwards and does not consider providers in the component from which you’re calling useContext().

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


更新通过上下文传递的数据

¥Updating data passed via context

通常,你会希望上下文随时间而改变。要更新上下文,请结合 状态。在父组件中声明一个状态变量,并将当前状态作为 上下文值 传递给提供器。

¥Often, you’ll want the context to change over time. To update context, combine it with state. Declare a state variable in the parent component, and pass the current state down as the context value to the 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>
);
}

现在,提供器内的任何 Button 都将收到当前的 theme 值。如果你调用 setTheme 来更新你传递给提供器的 theme 值,则所有 Button 组件都将使用新的 'light' 值重新渲染。

¥Now any Button inside of the provider will receive the current theme value. If you call setTheme to update the theme value that you pass to the provider, all Button components will re-render with the new 'light' value.

Examples of updating context

例子 1 / 5:
通过上下文更新值

¥Updating a value via context

在此示例中,MyApp 组件包含一个状态变量,然后将其传递给 ThemeContext 提供器。选中 “夜间模式” 复选框会更新状态。更改提供的值会使用该上下文重新渲染所有组件。

¥In this example, the MyApp component holds a state variable which is then passed to the ThemeContext provider. Checking the “Dark mode” checkbox updates the state. Changing the provided value re-renders all the components using that 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 变量的值。大括号还允许你传递不是字符串的上下文值。

¥Note that value="dark" passes the "dark" string, but value={theme} passes the value of the JavaScript theme variable with JSX curly braces. Curly braces also let you pass context values that aren’t strings.


指定回退默认值

¥Specifying a fallback default value

如果 React 在父树中找不到该特定 上下文 的任何提供器,useContext() 返回的上下文值将等于你在 创造该上下文 时指定的 默认值

¥If React can’t find any providers of that particular context in the parent tree, the context value returned by useContext() will be equal to the default value that you specified when you created that context:

const ThemeContext = createContext(null);

默认值永远不会改变。如果要更新上下文,请将其与状态一起使用,如上所述。

¥The default value never changes. If you want to update context, use it with state as described above.

通常,你可以将一些更有意义的值用作默认值,而不是 null,例如:

¥Often, instead of null, there is some more meaningful value you can use as a default, for example:

const ThemeContext = createContext('light');

这样,如果你不小心渲染了一些没有相应提供器的组件,它就不会崩溃。这也有助于你的组件在测试环境中良好运行,而无需在测试中设置大量提供器。

¥This way, if you accidentally render some component without a corresponding provider, it won’t break. This also helps your components work well in a test environment without setting up a lot of providers in the tests.

在下面的示例中,“切换主题” 按钮始终为浅色,因为它位于任何主题上下文提供程序之外,并且默认上下文主题值为 'light'。尝试将默认主题编辑为 'dark'

¥In the example below, the “Toggle theme” button is always light because it’s outside any theme context provider and the default context theme value is 'light'. Try editing the default theme to be '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>
  );
}


覆盖树的一部分的上下文

¥Overriding context for a part of the tree

你可以通过将树的一部分封装在具有不同值的提供器中来覆盖树的一部分的上下文。

¥You can override the context for a part of the tree by wrapping that part in a provider with a different value.

<ThemeContext.Provider value="dark">
...
<ThemeContext.Provider value="light">
<Footer />
</ThemeContext.Provider>
...
</ThemeContext.Provider>

你可以根据需要多次嵌套和覆盖提供器。

¥You can nest and override providers as many times as you need.

Examples of overriding context

例子 1 / 2:
覆盖主题

¥Overriding a theme

这里,Footer 内部的按钮接收到与外部按钮 ("dark") 不同的上下文值 ("light")。

¥Here, the button inside the Footer receives a different context value ("light") than the buttons outside ("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>
  );
}


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

¥Optimizing re-renders when passing objects and functions

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

¥You can pass any values via context, including objects and functions.

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) 的所有组件。

¥Here, the context value is a JavaScript object with two properties, one of which is a function. Whenever MyApp re-renders (for example, on a route update), this will be a different object pointing at a different function, so React will also have to re-render all components deep in the tree that call useContext(AuthContext).

在较小的应用中,这不是问题。但是,如果底层数据(如 currentUser)未更改,则无需重新渲染它们。为了帮助 React 利用这一事实,你可以使用 useCallback 封装 login 函数并将对象创建封装到 useMemo 中。这是一个性能优化:

¥In smaller apps, this is not a problem. However, there is no need to re-render them if the underlying data, like currentUser, has not changed. To help React take advantage of that fact, you may wrap the login function with useCallback and wrap the object creation into useMemo. This is a performance optimization:

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) 的组件将不需要重新渲染。

¥As a result of this change, even if MyApp needs to re-render, the components calling useContext(AuthContext) won’t need to re-render unless currentUser has changed.

阅读有关 useMemouseCallback 的更多信息。

¥Read more about useMemo and useCallback.


故障排除

¥Troubleshooting

我的组件看不到我的提供器的值

¥My component doesn’t see the value from my provider

有几种常见的方式可以发生这种情况:

¥There are a few common ways that this can happen:

  1. 你在调用 useContext() 的同一组件(或下方)中渲染 <SomeContext.Provider>。将 <SomeContext.Provider> 移到调用 useContext() 的组件上方和外部。

    ¥You’re rendering <SomeContext.Provider> in the same component (or below) as where you’re calling useContext(). Move <SomeContext.Provider> above and outside the component calling useContext().

  2. 你可能忘记了用 <SomeContext.Provider> 封装你的组件,或者你可能把它放在树中与你想象的不同的部分。使用 React 开发工具 检查层次结构是否正确。

    ¥You may have forgotten to wrap your component with <SomeContext.Provider>, or you might have put it in a different part of the tree than you thought. Check whether the hierarchy is right using React DevTools.

  3. 你的工具可能会遇到一些构建问题,导致从提供组件看到的 SomeContext 和从读取组件看到的 SomeContext 是两个不同的对象。例如,如果你使用符号链接,就会发生这种情况。你可以通过将它们分配给像 window.SomeContext1window.SomeContext2 这样的全局变量来验证这一点,然后在控制台中检查 window.SomeContext1 === window.SomeContext2 是否存在。如果它们不相同,请在构建工具级别修复该问题。

    ¥You might be running into some build issue with your tooling that causes SomeContext as seen from the providing component and SomeContext as seen by the reading component to be two different objects. This can happen if you use symlinks, for example. You can verify this by assigning them to globals like window.SomeContext1 and window.SomeContext2 and then checking whether window.SomeContext1 === window.SomeContext2 in the console. If they’re not the same, fix that issue on the build tool level.

尽管默认值不同,但我总是从上下文中获取到 undefined

¥I am always getting undefined from my context although the default value is different

你可能有一个在树中没有 value 的提供器:

¥You might have a provider without a value in the tree:

// 🚩 Doesn't work: no value prop
<ThemeContext.Provider>
<Button />
</ThemeContext.Provider>

如果忘记指定 value,就等于传递了 value={undefined}

¥If you forget to specify value, it’s like passing value={undefined}.

你可能还错误地使用了不同的属性名称:

¥You may have also mistakingly used a different prop name by mistake:

// 🚩 Doesn't work: prop should be called "value"
<ThemeContext.Provider theme={theme}>
<Button />
</ThemeContext.Provider>

在这两种情况下,你都应该在控制台中看到来自 React 的警告。要修复它们,请调用属性 value

¥In both of these cases you should see a warning from React in the console. To fix them, call the prop value:

// ✅ Passing the value prop
<ThemeContext.Provider value={theme}>
<Button />
</ThemeContext.Provider>

请注意,仅当上面根本没有匹配的提供程序时才使用 createContext(defaultValue) 调用的默认值。如果父树中某处存在 <SomeContext.Provider value={undefined}> 组件,则调用 useContext(SomeContext) 的组件将接收 undefined 作为上下文值。

¥Note that the default value from your createContext(defaultValue) call is only used if there is no matching provider above at all. If there is a <SomeContext.Provider value={undefined}> component somewhere in the parent tree, the component calling useContext(SomeContext) will receive undefined as the context value.


React 中文网 - 粤ICP备13048890号