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);
// ...
参数
¥Parameters
-
SomeContext
:你之前使用createContext
创建的上下文。上下文本身不包含信息,它仅表示你可以提供或从组件中读取的信息类型。¥
SomeContext
: The context that you’ve previously created withcreateContext
. 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.Provider
的 value
。如果没有这样的提供器,则返回值将是你为该上下文传递给 createContext
的 defaultValue
。返回值始终是最新的。如果上下文发生变化,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 theuseContext()
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 theObject.is
comparison. Skipping re-renders withmemo
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 andSomeContext
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.
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.
例子 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.
例子 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.
阅读有关 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.Provider>
。将<SomeContext.Provider>
移到调用useContext()
的组件上方和外部。¥You’re rendering
<SomeContext.Provider>
in the same component (or below) as where you’re callinguseContext()
. Move<SomeContext.Provider>
above and outside the component callinguseContext()
. -
你可能忘记了用
<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. -
你的工具可能会遇到一些构建问题,导致从提供组件看到的
SomeContext
和从读取组件看到的SomeContext
是两个不同的对象。例如,如果你使用符号链接,就会发生这种情况。你可以通过将它们分配给像window.SomeContext1
和window.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 andSomeContext
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 likewindow.SomeContext1
andwindow.SomeContext2
and then checking whetherwindow.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.