paint-brush
如何使用 React.js Context API 简化状态管理 - 教程经过@codebucks
新歷史

如何使用 React.js Context API 简化状态管理 - 教程

经过 CodeBucks11m2024/08/02
Read on Terminal Reader

太長; 讀書

本博客提供了使用 Context API 管理 React 状态的全面指南。它解释了如何避免 prop 钻取、提高性能以及有效地实现 Context API。通过实际示例和优化技巧,它非常适合希望简化 React 应用程序中状态管理的开发人员。
featured image - 如何使用 React.js Context API 简化状态管理 - 教程
CodeBucks HackerNoon profile picture
0-item

嗨,👋🏻,


本文专门为那些渴望学习更有效的方法来管理多个组件之间的状态的初学者而写。它还旨在解决 prop 钻取的常见问题,这个问题会使您的代码更难维护和理解。让我们先从 context API 解决什么问题开始。


如果您更喜欢视频格式,那么这里有您可以在我的 YouTube 频道上观看的教程。👇🏻


什么是支柱钻孔?

您知道有时需要将数据从父组件传递到子组件,而最终却要通过中间的一堆组件传递 props 吗?这称为prop 钻探,很快就会变得混乱。让我们通过一个例子来澄清这一点。


React.js 中的 Props 钻取

如图所示,假设您已在位于应用程序根目录的App组件中获取了一些数据。现在,如果深层嵌套的组件(例如Grandchild组件)需要访问此数据,您通常会将其作为 props 通过ParentChild组件向下传递,然后再到达Grandchild 。随着应用程序的增长,这可能会变得很糟糕。


以下是另一种视觉表示:


Reactjs Props 钻取示例

在上面的示例中, Profile组件需要用户数据,但这些数据必须先通过AppNavigation组件,即使这些中间组件本身并不使用这些数据。那么,我们如何清理这个问题呢?这时 Context API 就派上用场了。


道具钻孔:

  • 增加组件的重新渲染
  • 增加样板代码
  • 创建组件依赖关系
  • 降低性能

React 上下文 API

React.js 中的 Context API 可让您在组件之间传递数据,而无需将其作为 props 传递到组件树的每个级别。它就像一个全局状态管理系统,您可以在上下文对象中定义状态,然后可以轻松地在组件树中的任何位置访问它。让我们通过一个例子来理解这一点。


React.js 上下文 API

如图所示,我们有一个上下文对象,用于存储可供多个组件访问的数据。这些数据是从 API 或第三方服务获取的。在任何组件中访问此上下文数据之前,我们需要将所有需要此数据的组件包装在上下文提供程序组件中。


如果我们只需要访问导航和配置文件组件中的数据,则无需包装应用组件。使用ContextProvider包装相关组件后,您可以在使用它的任何组件中直接访问上下文数据。如果您还不理解,请不要担心;让我们深入研究代码并查看其实际效果。


如何使用 Context API?

首先,让我们使用Vite.js创建一个 React 应用。只需复制以下命令即可设置项目。


 npm create vite@latest


  • 添加您的项目名称
  • 选择 React
  • 从选项中选择 typescript
 cd project_name // to change to project directory npm install npm run dev


然后您可以在浏览器中打开您的开发服务器http://localhost:5173


首先,让我们创建所需的文件夹。这是我们项目的文件夹结构。

 src | components | context


在组件文件夹中,让我们创建Profile.jsx文件,并添加以下代码。

 import React from 'react' const Profile = () => { return ( <div>Profile</div> ) } export default Profile


在 components 文件夹中创建一个名为Navbar.jsx的组件。

 import Profile from './Profile' const Navbar = () => { return ( <nav style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "90%", height: "10vh", backgroundColor: theme === "light" ? "#fff" : "#1b1b1b", color: theme === "light" ? "#1b1b1b" : "#fff", border: "1px solid #fff", borderRadius: "5px", padding: "0 20px", marginTop: "40px", }}> <h1>LOGO</h1> <Profile /> </nav> ) } export default Navbar


让我们在App.jsx文件中导入这个<Navbar />组件。

 import Navbar from "./components/Navbar"; function App() { return ( <main style={{ display: "flex", flexDirection: "column", justifyContent: "start", alignItems: "center", height: "100vh", width: "100vw", }} > <Navbar /> </main> ); } export default App;


所以基本上, <Profile />组件是<Navbar />的子组件,而<Navbar /><App />组件的子组件。

添加上下文 API

让我们在context文件夹中创建UserContext.jsx文件。将以下代码添加到文件中。


 import { createContext, useEffect, useState } from "react"; export const UserContext = createContext(); export const UserProvider = ({ children }) => { const [user, setUser] = useState(null); const fetchUserData = async (id) => { const response = await fetch( `https://jsonplaceholder.typicode.com/users/${id}` ).then((response) => response.json()); console.log(response); setUser(response); }; useEffect(() => { fetchUserData(1); }, []); return ( <UserContext.Provider value={{ user, fetchUserData }} > {children} </UserContext.Provider> ); };


  • 首先,我们使用createContext创建一个空的UserContext对象。我们确保从react导入它。我们可以在 context 对象中添加默认值,但现在我们将其保留为 null。


  • 接下来,我们创建UserProvider ,它使用UserContext返回提供程序,例如UserContext.Provider 。它包裹子组件,在值中,我们可以传递要在子组件中使用的任何内容。


  • 目前,我们正在使用jsonplaceholder API来获取用户数据。jsonplaceholder 提供虚假的 API 端点用于测试目的。fetchUserData fetchUserData接受id并使用该 ID 来获取用户数据。然后我们将响应存储在user状态中。


  • 我们在useEffect中调用fetchUserData函数,因此在页面加载时,它会调用该函数,并在user状态中注入数据。


现在,让我们在<App />组件中使用这个上下文。使用<UserProvider />包装<NavBar />组件;与以下代码相同:

 <UserProvider> <Navbar /> </UserProvider>


让我们在<Profile />组件中使用user状态。为此,我们将使用useContext钩子。它接受UserContext并提供我们在UserProvider中传递的值,例如user状态和fetchUserData函数。请记住,我们不需要包装<Profile />组件,因为它已经在<Navbar />组件中,该组件已经用提供程序包装。


打开Profile.jsx ,并添加以下代码。

 const { user } = useContext(UserContext); if (user) { return ( <span style={{ fontWeight: "bold", }} > {user.name} </span> ); } else { return <span>Login</span>; }


这里,我们使用来自UserContextuser状态。如果有user ,我们将显示用户名,否则,我们将仅显示登录消息。现在,如果您看到输出,则导航栏组件中应该有一个用户名。这就是我们可以直接使用任何组件上下文中的任何状态的方式。使用此状态的组件应包装在<Provider />中。


您还可以使用多个上下文。您只需将提供程序组件包装在另一个提供程序组件中,如以下示例所示。

 <ThemeProvider> <UserProvider> <Navbar /> </UserProvider> </ThemeProvider>


在上面的例子中,我们使用<ThemeProvider />来管理主题状态。


您可以观看上面的 YouTube 视频来查看使用多个上下文提供程序的完整示例。

优化 React Context API 中的重新渲染

在多个组件中使用 Context API 时会出现一个问题。每当 Context API 中的状态或值发生变化时,它都会重新渲染订阅该特定上下文的所有组件,即使并非所有组件都在使用更改的状态。为了理解这个重新渲染问题,让我们创建一个使用上下文来存储和显示计数值的<Counter />组件。


查看以下示例。您可以在 components 文件夹中创建一个Counter.jsx文件并粘贴以下代码。


 import { createContext, memo, useContext, useState } from "react"; const CountContext = createContext(); const CountProvider = ({ children }) => { const [count, setCount] = useState(0); return ( <CountContext.Provider value={{ count, setCount }}> {children} </CountContext.Provider> ); }; function CountTitle() { console.log("This is Count Title component"); return <h1>Counter Title</h1>; } function CountDisplay() { console.log("This is CountDisplay component"); const { count } = useContext(CountContext); return <div>Count: {count}</div>; } function CounterButton() { console.log("This is CounterButton component"); const { count, setCount } = useContext(CountContext); return ( <> <CountTitle /> <CountDisplay /> <button onClick={() => setCount(count + 1)}>Increase</button> </> ); } export default function Counter() { return ( <CountProvider> <CounterButton /> </CountProvider> ); }


在上面的代码中:

  • 首先,我们使用 createContext 创建一个CountContext对象createContext.


  • CountProvider,我们有一个状态来存储计数值。我们通过 value prop 将countsetCount方法发送给子组件。


  • 我们分别创建了组件,以查看各个组件重新渲染的次数。

    • <CountTitle /> :此组件仅显示标题,甚至不使用上下文中的任何值。

    • <CountDisplay /> :此组件显示计数值并使用上下文中的count状态。

    • <CounterButton /> :此组件同时呈现上述组件和使用setCount增加计数值的按钮。


  • 最后,我们将<CounterButton />组件包装在CountProvider组件中,以便其他组件可以访问计数值。


现在,如果您运行代码并单击Increase按钮,您将在日志中看到,每次状态更改时,每个组件都会重新渲染。 <CountTitle />甚至没有使用计数值,但它正在重新渲染。发生这种情况是因为<CountTitle />的父组件<CounterButton />正在使用和更新计数值,这就是重新渲染的原因。


我们如何优化这种行为?答案是memo 。React memo可让您在组件的 props 不变时跳过重新渲染组件。在<CountTitle />组件之后,让我们添加以下行。


 const MemoizedCountTitle = React.memo(CountTitle)


现在,在<CounterButton />组件中,我们正在渲染<CountTitle />组件,将<CountTitle />替换为<MemoizedCountTitle />如以下代码所示:


 <> <MemoizedCountTitle /> <CountDisplay /> <button onClick={() => setCount(count + 1)}>Increase</button> </>


现在,如果您增加数量并检查日志,您应该能够看到它不再渲染<CountTitle />组件。

Redux 与 Context API

Redux是一个状态管理库,用于管理复杂的状态,具有更可预测的状态转换。而 Context API 则设计用于简单的状态管理,并通过组件树传递数据而无需 prop 钻取。那么,什么时候该选择哪一个呢?


  • 使用React Context API进行简单、本地化的状态管理,其中状态不会频繁改变。


  • 使用Redux满足复杂的状态管理需求,特别是在较大的应用程序中,其结构化状态管理的好处超过额外的设置。


还有一个库也是一个流行的状态管理选项。React Recoil


  • React Recoil是 React 的状态管理库,旨在提供 Context API 的简单性以及 Redux 的强大功能和性能。


如果您有兴趣了解有关React Recoil 的更多信息,请在评论中告诉我,我将根据您的反馈创建有关该主题的深入教程。

结论

React.js Context API 提供了一种强大而有效的方法来管理多个组件的状态,有效地解决了 prop 钻取的问题。通过使用 Context API,您可以简化代码、减少不必要的重新渲染并提高整体应用程序性能。


虽然 Context API 非常适合简单的状态管理,但更复杂的应用程序可能会受益于使用Redux或其他状态管理库(如React Recoil 。了解何时以及如何使用这些工具将使您能够构建更易于维护和可扩展的 React 应用程序。


感谢您阅读本文,希望对您有所帮助。如果您有兴趣学习并使用 React、Redux 和 Next.js 构建项目,可以访问我的 YouTube 频道: CodeBucks


以下是您可能想阅读的我的其他文章:

访问我的个人博客: DevDreaming