useContext 是一个 React Hook,它允许你从组件中读取和订阅 context。
const value = useContext(SomeContext)参考
🌐 Reference
useContext(SomeContext)
在组件的顶层调用 useContext 来读取并订阅 context.
🌐 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);
// ...参数
🌐 Parameters
SomeContext:你之前与createContext创建的上下文。上下文本身不包含信息,它只是表示你可以从组件提供或读取的信息类型。
返回
🌐 Returns
useContext 返回调用组件的上下文值。它由传递给调用组件树中最近 SomeContext 的 value 决定。如果没有这样的提供者,则返回的值将是你为该上下文传递给 createContext 的 defaultValue。返回的值始终是最新的。如果上下文发生变化,React 会自动重新渲染读取该上下文的组件。
注意事项
🌐 Caveats
useContext()在组件中的调用不受 同一 组件返回的提供者影响。相应的<Context>需要位于 执行useContext()调用的组件之上。- React 会自动重新渲染 所有使用特定上下文的子组件,从接收到不同
value的提供者开始。以前的值和下一个值会通过Object.is进行比较。使用memo跳过重新渲染并不会阻止子组件接收最新的上下文值。 - 如果你的构建系统在输出中产生重复模块(这在使用符号链接时可能发生),这可能会破坏上下文。通过上下文传递某些内容只有在你用于提供上下文的
SomeContext和你用于读取它的SomeContext完全相同的对象 时才有效,这由===比较确定。
用法
🌐 Usage
将数据深入传递到树中
🌐 Passing data deeply into the tree
在组件的顶层调用 useContext 来读取并订阅 context.
🌐 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 返回你传入的 <CodeStep step={1}>context</CodeStep> 的 <CodeStep step={2}>context value</CodeStep>。为了确定上下文值,React 会搜索组件树,并找到该特定上下文上方最近的上下文提供者。
要将上下文传递给 Button,请将其或其某个父组件封装到相应的上下文提供者中:
🌐 To pass context to a Button, wrap it or one of its parent components into the corresponding context provider:
function MyPage() {
return (
<ThemeContext value="dark">
<Form />
</ThemeContext>
);
}
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.
import { createContext, useContext } from 'react'; const ThemeContext = createContext(null); export default function MyApp() { return ( <ThemeContext value="dark"> <Form /> </ThemeContext> ) } function Form() { return ( <Panel title="欢迎"> <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
通常,你会希望上下文随时间变化。要更新上下文,请将其与state.结合。在父组件中声明一个状态变量,并将当前状态作为 context value 传递给提供者。
function MyPage() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext value={theme}>
<Form />
<Button onClick={() => {
setTheme('light');
}}>
Switch to light theme
</Button>
</ThemeContext>
);
}现在,提供者内部的任何 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.
例子 1 of 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 value={theme}> <Form /> <label> <input type="checkbox" checked={theme === 'dark'} onChange={(e) => { setTheme(e.target.checked ? 'dark' : 'light') }} /> Use dark mode </label> </ThemeContext> ) } function Form({ children }) { return ( <Panel title="欢迎"> <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 大括号。大括号也允许你传递不是字符串的上下文值。
🌐 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 在父组件树中找不到该特定 context 的任何提供者,useContext() 返回的上下文值将等于你在创建该上下文时指定的 默认值 :
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 value={theme}> <Form /> </ThemeContext> <Button onClick={() => { setTheme(theme === 'dark' ? 'light' : 'dark'); }}> Toggle theme </Button> </> ) } function Form({ children }) { return ( <Panel title="欢迎"> <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 value="dark">
...
<ThemeContext value="light">
<Footer />
</ThemeContext>
...
</ThemeContext>你可以根据需要多次嵌套和覆盖提供器。
🌐 You can nest and override providers as many times as you need.
例子 1 of 2: 覆盖主题
🌐 Overriding a theme
在这里,Footer 内部的按钮接收的上下文值("light")与外部的按钮("dark")不同。
🌐 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 value="dark"> <Form /> </ThemeContext> ) } function Form() { return ( <Panel title="欢迎"> <Button>Sign up</Button> <Button>Log in</Button> <ThemeContext value="light"> <Footer /> </ThemeContext> </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 value={{ currentUser, login }}>
<Page />
</AuthContext>
);
}这里, context 值 是一个具有两个属性的 JavaScript 对象,其中一个属性是函数。每当 MyApp 重新渲染(例如,在路由更新时),这将是一个指向 不同 函数的 不同 对象,因此 React 也必须重新渲染树中调用 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 value={contextValue}>
<Page />
</AuthContext>
);
}由于这一变化,即使 MyApp 需要重新渲染,调用 useContext(AuthContext) 的组件也不需要重新渲染,除非 currentUser 已经改变。
🌐 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.
阅读更多关于 useMemo 和 useCallback 的内容
🌐 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:
- 你正在在与调用
useContext()相同的组件中(或以下)渲染<SomeContext>。将<SomeContext>移动到调用useContext()的组件的上方和外部。 - 你可能忘记用
<SomeContext>封装你的组件,或者你可能把它放在了树的其他位置。使用 React DevTools 检查层级是否正确。 - 你可能在构建工具上遇到一些问题,这导致从提供组件看到的
SomeContext和从读取组件看到的SomeContext是两个不同的对象。例如,如果你使用符号链接,这种情况就可能发生。你可以通过将它们分配给全局变量如window.SomeContext1和window.SomeContext2来验证,然后在控制台检查window.SomeContext1 === window.SomeContext2是否一样。如果它们不相同,请在构建工具层面解决该问题。
尽管默认值不同,我总是从我的上下文中得到 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>
<Button />
</ThemeContext>如果你忘记指定 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 theme={theme}>
<Button />
</ThemeContext>在这两种情况下,你都应该在控制台中看到 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 value={theme}>
<Button />
</ThemeContext>请注意,来自你的 createContext(defaultValue) 调用的默认值仅在上面完全没有匹配的提供者时使用。如果父组件树的某处存在 <SomeContext 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 value={undefined}> component somewhere in the parent tree, the component calling useContext(SomeContext) will receive undefined as the context value.