本文作者为 360 奇舞团前端开发工程师
本文将详细介绍如何使用 React 的 Context API 优雅地实现多主题切换,解决 props 穿透问题,并避免不必要的重新渲染。通过具体的示例代码,展示如何在浅色和深色模式之间切换,并探讨在实际项目中管理多个 Context 的最佳实践。
目录
-
什么是 React Context API,何时使用?
-
如何创建多个 React 上下文(以及为什么应该这样做)
如何防止 React Context 重新渲染问题
什么是 React Context API,何时使用?
React Context API 是 React 库的一部分,它允许在组件之间共享全局数据,而无需通过每层组件传递
props
。Context API 非常适合需要在多个嵌套组件中共享状态的场景,例如管理全局主题设置、用户身份验证状态或应用配置等。使用 Context API,可以避免繁琐的 props 传递,提高代码的可读性和维护性。
以下内容将通过具体示例展示如何在 React 应用中使用 Context API 实现多主题切换,并探讨其最佳实践。
浅色和深色模式 UI 主题
React Context 的一个常见应用是管理浅色和深色模式的 UI 主题。许多 UI 组件,如按钮、标题、导航栏等,都需要根据当前主题显示不同的样式。通过使用 Context,可以在整个应用中方便地共享和切换主题,而不需要在每个组件中手动传递
props
。下面我们对比一下两种解决方案:使用Props传递的方案和Context API 解决方案
使用 Props 传递的方案
最直接的方法是通过顶层组件创建一个主题变量,然后将其作为 props 传递给组件树中的所有子组件。然而,这种方法会导致“props 穿透”问题,即每个中间组件都需要传递这个 props,即使它们并不实际使用该值。这不仅使代码变得冗长和难以维护,还可能导致中间组件在不必要的时候重新渲染,从而影响性能。
function App() {
const theme = 'dark';
return <Parent theme={theme} />;
}
function Parent({ theme }) {
return <Child theme={theme} />;
}
function Child({ theme }) {
return <Switch theme={theme} />;
}
function Switch({ theme }) {
return <Switch style={{ background: theme === 'dark' ? '#000' : '#fff' }}>切换主题Switch>;
}
在上述代码中,
theme
属性被一层层传递到最底层的
Switch
组件。虽然这种方式能实现功能,但显然并不优雅。每个中间组件都需要接受和传递
theme
属性,即使它们并不使用这个值。这不仅增加了代码的复杂度,还导致了潜在的性能问题。
Context API 解决方案
我们可以通过使用 Context API 来解决
props 穿透
问题。
创建 Context
首先,我们需要引入
createContext
,配置所需的主题颜色,并使用
light
主题作为默认值:
// src/contexts/ThemeContext.js
import { createContext } from "react";
export const themes = {
light: {
background: "#fff",
text: "#000",
current: 'light'
},
dark: {
background: "#000",
text: "#fff",
},
};
export const ThemeContext = createContext(themes.light);
创建 Provider 组件
接下来,我们需要创建一个组件,通过 Context 的 Provider 来提供全局状态。这个组件通常会包含状态和操作方法,并将它们作为值传递给 Provider。例如,以下的
和
组件将可以访问
theme
状态:
// src/App.js
import React, { useState } from "react"
import { ThemeContext, themes } from "./contexts/ThemeContext"
import Navbar from "./components/Navbar"
import Switch from "./components/Switch"
const App = () => {
const [theme, setTheme] = useState(themes.light)
const toggleTheme = () => {
setTheme(state => (state === themes.light ? themes.dark : themes.light))
}
return (
<div className="App">
<ThemeContext.Provider value={theme}>
<Navbar />
<Switch changeTheme={toggleTheme} />
ThemeContext.Provider>
div>
)
}
export default App
在上面的代码中,通过
ThemeContext.Provider
将
theme
和
setTheme
方法传递给其子组件。这样,在
Switch
和
Navbar
组件中可以使用
useContext
钩子访问
ThemeContext
。
使用 Context
在
Switch
和
Navbar
组件中,我们使用
useContext
钩子来获取当前主题,并根据主题动态更改样式:
// src/components/Switch.js
import React, { useContext } from "react"
import { ThemeContext } from "../contexts/themeContext"
const Switch = ({ changeTheme }) => {
const theme = useContext(ThemeContext)
return (
<Switch
style={{ backgroundColor: theme.background, color: theme.text }}
onClick={changeTheme}
>
切换主题:{ theme.current }
Switch>
)
}
export default Switch
// src/components/Navbar.js
import React, { useContext } from "react"
import { ThemeContext } from "../contexts/themeContext"
const Navbar = () => {
const theme = useContext(ThemeContext)
return (
<div style={{ backgroundColor: theme.background }}>
<ul style={{ display: "flex",gap: "20px" }}>
<li style={{ color: theme.text }}>首页li>
<li style={{ color: theme.text }}>广场li>
<li style={{ color: theme.text}}>我的li>
ul>
div>
)
}
export default Navbar
通过这样设置,组件可以访问全局变量,每次更新上下文中的值时,组件都会重新渲染。
浅色模式:
深色模式:
如何创建多个 React Contexts
在上面的示例中,我们只创建了一个上下文,
即 ThemeContext
。但是,如果我们还有其他需要全局使用的数据,例如当前登录用户的信息
username
和
age
,该怎么办?我们可以创建一个大的
Context
来存储需要全局使用的所有变量:
<Switch changeTheme={toggleTheme} />
<Navbar />
</GlobalContext.Provider>
但这并不是一个好的做法。因为每当
Context
的值更新时,使用该
Context
的所有组件都会重新渲染。这意味着,每当更新任何用户的变量信息时,所有只关心主题变化而不需要关心用户数据变化的组件也会被重新渲染。这可能会降低应用的性能,尤其是在具有大量复杂组件的应用中。
为了解决这个问题,我们可以创建多个
Context
。例如,一个用于管理主题(
ThemeContext
),另一个用于管理用户数据(
UserContext
)。如下所示:
// src/contexts/UserContext.js
import { createContext } from "react";
export const UserContext = createContext({
username: "",
age: 0,
});
// src/App.js
import React, { useState } from "react";
import { ThemeContext, themes } from "./contexts/ThemeContext";
import { UserContext } from "./contexts/UserContext";
import Navbar from "./components/Navbar";
import Switch from "./components/Switch";
const App = () => {
const [theme, setTheme] = useState(themes.light);
const [user, setUser] = useState({ username: "mario", age: 25 });
const toggleTheme = () => {
setTheme(state => (state === themes.light ? themes.dark : themes.light));
};
return (
<div className="App">
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={user}>
<Navbar />
<Switch changeTheme={toggleTheme} />
UserContext.Provider>
ThemeContext.Provider>
div>
);
};
export default App;
通过这种方式,每个 context 中只存储与其相关的数据,这有助于防止不必要的组件重新渲染,从而提高应用程序的性能。
如何防止 React Context 重新渲染问题
正如上面所写的,每当更新
Context
值时,所有使用该上下文的组件都将被重新渲染——即使包装在
React.memo()
中也是如此。但我们可以通过以下方法缓解这个问题: