嗨,👋🏻,
本文专门为那些渴望学习更有效的方法来管理多个组件之间的状态的初学者而写。它还旨在解决 prop 钻取的常见问题,这个问题会使您的代码更难维护和理解。让我们先从 context API 解决什么问题开始。
如果您更喜欢视频格式,那么这里有您可以在我的 YouTube 频道上观看的教程。👇🏻
您知道有时需要将数据从父组件传递到子组件,而最终却要通过中间的一堆组件传递 props 吗?这称为prop 钻探,很快就会变得混乱。让我们通过一个例子来澄清这一点。
如图所示,假设您已在位于应用程序根目录的App
组件中获取了一些数据。现在,如果深层嵌套的组件(例如Grandchild
组件)需要访问此数据,您通常会将其作为 props 通过Parent
和Child
组件向下传递,然后再到达Grandchild
。随着应用程序的增长,这可能会变得很糟糕。
以下是另一种视觉表示:
在上面的示例中, Profile
组件需要用户数据,但这些数据必须先通过App
和Navigation
组件,即使这些中间组件本身并不使用这些数据。那么,我们如何清理这个问题呢?这时 Context API 就派上用场了。
道具钻孔:
React.js 中的 Context API 可让您在组件之间传递数据,而无需将其作为 props 传递到组件树的每个级别。它就像一个全局状态管理系统,您可以在上下文对象中定义状态,然后可以轻松地在组件树中的任何位置访问它。让我们通过一个例子来理解这一点。
如图所示,我们有一个上下文对象,用于存储可供多个组件访问的数据。这些数据是从 API 或第三方服务获取的。在任何组件中访问此上下文数据之前,我们需要将所有需要此数据的组件包装在上下文提供程序组件中。
如果我们只需要访问导航和配置文件组件中的数据,则无需包装应用组件。使用ContextProvider
包装相关组件后,您可以在使用它的任何组件中直接访问上下文数据。如果您还不理解,请不要担心;让我们深入研究代码并查看其实际效果。
首先,让我们使用Vite.js创建一个 React 应用。只需复制以下命令即可设置项目。
npm create vite@latest
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 />
组件的子组件。
让我们在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
。它包裹子组件,在值中,我们可以传递要在子组件中使用的任何内容。
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>; }
这里,我们使用来自UserContext
的user
状态。如果有user
,我们将显示用户名,否则,我们将仅显示登录消息。现在,如果您看到输出,则导航栏组件中应该有一个用户名。这就是我们可以直接使用任何组件上下文中的任何状态的方式。使用此状态的组件应包装在<Provider />
中。
您还可以使用多个上下文。您只需将提供程序组件包装在另一个提供程序组件中,如以下示例所示。
<ThemeProvider> <UserProvider> <Navbar /> </UserProvider> </ThemeProvider>
在上面的例子中,我们使用<ThemeProvider />
来管理主题状态。
您可以观看上面的 YouTube 视频来查看使用多个上下文提供程序的完整示例。
在多个组件中使用 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> ); }
在上面的代码中:
CountContext
对象createContext.
CountProvider,
我们有一个状态来存储计数值。我们通过 value prop 将count
和setCount
方法发送给子组件。
我们分别创建了组件,以查看各个组件重新渲染的次数。
<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 则设计用于简单的状态管理,并通过组件树传递数据而无需 prop 钻取。那么,什么时候该选择哪一个呢?
还有一个库也是一个流行的状态管理选项。React Recoil 。
如果您有兴趣了解有关React Recoil 的更多信息,请在评论中告诉我,我将根据您的反馈创建有关该主题的深入教程。
React.js
Context API 提供了一种强大而有效的方法来管理多个组件的状态,有效地解决了 prop 钻取的问题。通过使用 Context API,您可以简化代码、减少不必要的重新渲染并提高整体应用程序性能。
虽然 Context API 非常适合简单的状态管理,但更复杂的应用程序可能会受益于使用Redux
或其他状态管理库(如React Recoil
。了解何时以及如何使用这些工具将使您能够构建更易于维护和可扩展的 React 应用程序。
感谢您阅读本文,希望对您有所帮助。如果您有兴趣学习并使用 React、Redux 和 Next.js 构建项目,可以访问我的 YouTube 频道: CodeBucks
以下是您可能想阅读的我的其他文章:
访问我的个人博客: DevDreaming