React Hooks 系列 之 createContext、useContext
在 React 中,上下文(Context)API 提供了一个强大的方法,允许我们在组件树中轻松地传递数据,而不必手动将 props 传递到每一个层级。在本文中,我们将通过一个实际的示例来探讨如何使用 createContext
、useContext
。
逻辑示例:用户偏好设置
假设我们正在构建一个应用,用户可以选择应用的主题颜色和字体大小。我们希望这些设置在整个应用中都可用。
1. 创建上下文
首先,我们需要定义我们的上下文和默认值:
javascript
const UserPreferencesContext = React.createContext({
theme: "light",
fontSize: "medium",
});
const UserPreferencesContext = React.createContext({
theme: "light",
fontSize: "medium",
});
2. 使用 Provider
为了使我们的偏好设置在整个应用中都可用,我们需要在应用的顶层使用 Provider
:
javascript
function App() {
const [preferences, setPreferences] = React.useState({
theme: "light",
fontSize: "medium",
});
return (
<UserPreferencesContext.Provider value={{ preferences, setPreferences }}>
<Navbar />
<Content />
</UserPreferencesContext.Provider>
);
}
function App() {
const [preferences, setPreferences] = React.useState({
theme: "light",
fontSize: "medium",
});
return (
<UserPreferencesContext.Provider value={{ preferences, setPreferences }}>
<Navbar />
<Content />
</UserPreferencesContext.Provider>
);
}
3. 使用 useContext
在函数组件中,我们可以使用 useContext
Hook 来访问上下文:
javascript
function Navbar() {
const { preferences } = React.useContext(UserPreferencesContext);
return (
<nav
style={{
backgroundColor: preferences.theme === "dark" ? "#333" : "#FFF",
}}
>
{/* ... */}
</nav>
);
}
function Navbar() {
const { preferences } = React.useContext(UserPreferencesContext);
return (
<nav
style={{
backgroundColor: preferences.theme === "dark" ? "#333" : "#FFF",
}}
>
{/* ... */}
</nav>
);
}
4. 使用 Consumer
在类组件或需要更复杂的渲染逻辑的组件中,我们可以使用 Consumer
:
javascript
class Content extends React.Component {
render() {
return (
<UserPreferencesContext.Consumer>
{({ preferences, setPreferences }) => (
<div>
<p
style={{
fontSize: preferences.fontSize === "large" ? "20px" : "16px",
}}
>
This is some content.
</p>
<button
onClick={() =>
setPreferences((prev) => ({ ...prev, theme: "dark" }))
}
>
Switch to Dark Theme
</button>
</div>
)}
</UserPreferencesContext.Consumer>
);
}
}
class Content extends React.Component {
render() {
return (
<UserPreferencesContext.Consumer>
{({ preferences, setPreferences }) => (
<div>
<p
style={{
fontSize: preferences.fontSize === "large" ? "20px" : "16px",
}}
>
This is some content.
</p>
<button
onClick={() =>
setPreferences((prev) => ({ ...prev, theme: "dark" }))
}
>
Switch to Dark Theme
</button>
</div>
)}
</UserPreferencesContext.Consumer>
);
}
}
完整示例:用户偏好设置
上述逻辑示例的具体实现:
demo 代码
jsx
import React, { useState, createContext, useContext } from "react";
import { Card, Button } from "antd";
// 1. 创建上下文
const UserPreferencesContext = createContext({
theme: "light",
fontSize: "medium",
});
function App() {
const [preferences, setPreferences] = useState({
theme: "light",
fontSize: "medium",
});
// 2. 使用 Provider
return (
<Card title="案例 demo">
<UserPreferencesContext.Provider value={{ preferences, setPreferences }}>
<Navbar />
<Content />
</UserPreferencesContext.Provider>
</Card>
);
}
// 3. 使用 useContext
function Navbar() {
const { preferences } = useContext(UserPreferencesContext);
return (
<nav
style={{
backgroundColor: preferences.theme === "dark" ? "#333" : "#FFF",
color: preferences.theme === "dark" ? "#FFF" : "#333",
padding: "10px",
}}
>
App Navbar
</nav>
);
}
// 4. 使用 Consumer
class Content extends React.Component {
render() {
return (
<UserPreferencesContext.Consumer>
{({ preferences, setPreferences }) => (
<div style={{ padding: "20px" }}>
<p
style={{
fontSize: preferences.fontSize === "large" ? "20px" : "16px",
}}
>
This is some content.
</p>
<Button
onClick={() =>
setPreferences((prev) => ({
...prev,
theme: prev.theme === "light" ? "dark" : "light",
}))
}
type="primary"
>
Toggle Theme
</Button>
<Button
onClick={() =>
setPreferences((prev) => ({
...prev,
fontSize: prev.fontSize === "medium" ? "large" : "medium",
}))
}
style={{ marginLeft: "10px" }}
type="primary"
>
Toggle Font Size
</Button>
</div>
)}
</UserPreferencesContext.Consumer>
);
}
}
export default App;
import React, { useState, createContext, useContext } from "react";
import { Card, Button } from "antd";
// 1. 创建上下文
const UserPreferencesContext = createContext({
theme: "light",
fontSize: "medium",
});
function App() {
const [preferences, setPreferences] = useState({
theme: "light",
fontSize: "medium",
});
// 2. 使用 Provider
return (
<Card title="案例 demo">
<UserPreferencesContext.Provider value={{ preferences, setPreferences }}>
<Navbar />
<Content />
</UserPreferencesContext.Provider>
</Card>
);
}
// 3. 使用 useContext
function Navbar() {
const { preferences } = useContext(UserPreferencesContext);
return (
<nav
style={{
backgroundColor: preferences.theme === "dark" ? "#333" : "#FFF",
color: preferences.theme === "dark" ? "#FFF" : "#333",
padding: "10px",
}}
>
App Navbar
</nav>
);
}
// 4. 使用 Consumer
class Content extends React.Component {
render() {
return (
<UserPreferencesContext.Consumer>
{({ preferences, setPreferences }) => (
<div style={{ padding: "20px" }}>
<p
style={{
fontSize: preferences.fontSize === "large" ? "20px" : "16px",
}}
>
This is some content.
</p>
<Button
onClick={() =>
setPreferences((prev) => ({
...prev,
theme: prev.theme === "light" ? "dark" : "light",
}))
}
type="primary"
>
Toggle Theme
</Button>
<Button
onClick={() =>
setPreferences((prev) => ({
...prev,
fontSize: prev.fontSize === "medium" ? "large" : "medium",
}))
}
style={{ marginLeft: "10px" }}
type="primary"
>
Toggle Font Size
</Button>
</div>
)}
</UserPreferencesContext.Consumer>
);
}
}
export default App;
嵌套上下文
上下文可以嵌套,这意味着我们可以在应用的不同部分使用不同的上下文。当有多个嵌套的 Provider 时,最近的 Provider(即最内层的 Provider)的值会覆盖外部 Provider 的值。
demo 代码
jsx
import React, { useContext } from "react";
import { Card } from "antd";
const ThemeContext = React.createContext("default");
function Footer() {
const theme = useContext(ThemeContext);
return <div>The theme in Footer is: {theme}</div>;
}
function NestedTheme() {
return (
<Card title="案例 demo">
<ThemeContext.Provider value="dark">
<div>The theme outside Footer is: dark</div>
<ThemeContext.Provider value="light">
<Footer />
</ThemeContext.Provider>
</ThemeContext.Provider>
</Card>
);
}
export default NestedTheme;
import React, { useContext } from "react";
import { Card } from "antd";
const ThemeContext = React.createContext("default");
function Footer() {
const theme = useContext(ThemeContext);
return <div>The theme in Footer is: {theme}</div>;
}
function NestedTheme() {
return (
<Card title="案例 demo">
<ThemeContext.Provider value="dark">
<div>The theme outside Footer is: dark</div>
<ThemeContext.Provider value="light">
<Footer />
</ThemeContext.Provider>
</ThemeContext.Provider>
</Card>
);
}
export default NestedTheme;
调用 createContext、useContext 后大致执行情况
null