使用 React Context API 的最佳实践

本文作者为 360 奇舞团前端开发工程师

本文将详细介绍如何使用 React 的 Context API 优雅地实现多主题切换,解决 props 穿透问题,并避免不必要的重新渲染。通过具体的示例代码,展示如何在浅色和深色模式之间切换,并探讨在实际项目中管理多个 Context 的最佳实践。

目录

  1. 什么是 React Context API,何时使用?

  2. 在浅色和深色模式之间切换 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.ProviderthemesetTheme 方法传递给其子组件。这样,在 SwitchNavbar 组件中可以使用 useContext 钩子访问 ThemeContext

使用 Context

SwitchNavbar 组件中,我们使用 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

通过这样设置,组件可以访问全局变量,每次更新上下文中的值时,组件都会重新渲染。

浅色模式:

c6650af4210ecf6d447103869899c89a.png
image-20240804200934957

深色模式:

cdb23a0123e1f4350340842b81c71363.png
image-20240804201002336

如何创建多个 React Contexts

在上面的示例中,我们只创建了一个上下文,即 ThemeContext。但是,如果我们还有其他需要全局使用的数据,例如当前登录用户的信息 usernameage,该怎么办?我们可以创建一个大的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 相关领域可以扫码进群交流

785501924b42edf34134051b8dcfc388.png 67ddb5ff28e074f16d8e9deabd90f446.jpeg

扫码进群2或添加小编微信进群1😊

关于奇舞团

奇舞团是 360 集团最大的大前端团队,非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

83226ebc7285a02d55fa5e84bf0617c1.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值