本文作者为 360 奇舞团前端开发工程师
本文将详细介绍如何使用 React 的 Context API 优雅地实现多主题切换,解决 props 穿透问题,并避免不必要的重新渲染。通过具体的示例代码,展示如何在浅色和深色模式之间切换,并探讨在实际项目中管理多个 Context 的最佳实践。
目录
什么是 React Context API,何时使用?
在浅色和深色模式之间切换 UI 主题
使用属性传递方案
Context API 解决方案
如何创建多个 React 上下文(以及为什么应该这样做)
如何防止 React Context 重新渲染问题
使用多个 React Context
拆分组件并传递所需的值
使用 React.useMemo() 组件
什么是 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。例如,以下的 <Navbar />
和 <Switch />
组件将可以访问 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
来存储需要全局使用的所有变量:
<GlobalContext.Provider value={{ theme, username, age }}>
<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()
中也是如此。但我们可以通过以下方法缓解这个问题:
1. 使用多个 React Context
这是防止不必要重新渲染的首选方法。通过创建多个 context,将相关的数据分开存储,只有使用特定 context 的组件会因更新而重新渲染。
2. 拆分组件并传递所需的值
通过将组件拆分,并将所需的值作为 props 从 context 中传递,并将子组件包装在 React.memo
中。React.memo
是一个高阶组件(HOC),用于优化函数组件,通过缓存组件防止不必要的重新渲染。只有当其 props 发生变化时,组件才会重新渲染。
const Card = () => {
const appContextValue = useContext(AppContext);
const theme = appContextValue.theme;
return (
<div>
<CardTitle theme={theme} />
<CardDescription theme={theme} />
</div>
);
};
const CardTitle = React.memo(({ theme }) => {
return <h2 style={{ color: theme.text }}>2024年巴黎奥运会 </h2>;
});
const CardDescription = React.memo(({ theme }) => {
return <p style={{ color: theme.text }}>2024年巴黎奥运会是第33届夏季奥林匹克运动会</p>;
});
3. 使用 React.useMemo()
通过将组件包装在 useMemo
中,并将theme
作为依赖项,只有当theme
更改时才会触发回调函数重新渲染组件。
const Card = () => {
const appContextValue = useContext(AppContext);
const theme = appContextValue.theme;
return useMemo(
() => (
<div>
<CardTitle theme={theme} />
<CardDescription theme={theme} />
</div>
),
[theme]
);
};
const CardTitle = ({ theme }) => {
return <h2 style={{ color: theme.text }}>2024年巴黎奥运会 </h2>;
};
const CardDescription = ({ theme }) => {
return <p style={{ color: theme.text }}>2024年巴黎奥运会是第33届夏季奥林匹克运动会</p>;
};
通过这些方法,可以有效减少 React Context 造成的不必要重新渲染,提升应用的性能。
总结
通过本文的学习,我们了解了如何利用 React 的 Context API 高效地实现多主题切换。我们探讨了使用 Context 解决 props 穿透问题的方案,展示了在应用中创建和管理多个 Context 的方法,并提供了避免不必要重新渲染的优化技巧。这些实践不仅能提升代码的可维护性,还能显著改善用户体验。在实际开发中,灵活运用这些技术,将帮助我们构建更为出色的 React 应用。
- END -
如果您关注前端+AI 相关领域可以扫码进群交流
扫码进群2或添加小编微信进群1😊
关于奇舞团
奇舞团是 360 集团最大的大前端团队,非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。