안녕하세요👋🏻,
이 문서는 여러 구성 요소 간의 상태를 관리하는 보다 효과적인 방법을 배우고 싶어하는 초보자를 위해 특별히 작성되었습니다. 또한 코드를 유지 관리하고 이해하기 어렵게 만드는 일반적인 소품 드릴링 문제를 해결하는 것을 목표로 합니다. 어떤 종류의 문제 컨텍스트 API가 해결하는지부터 시작해 보겠습니다.
비디오 형식을 선호한다면 여기 내 YouTube 채널에서 시청할 수 있는 튜토리얼이 있습니다.👇🏻
부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달해야 하는 경우가 있는데, 그 사이에 여러 컴포넌트를 통해 props를 전달하게 되는 경우가 있다는 것을 알고 계십니까? 이를 프롭 드릴링 이라고 하며 빠르게 지저분해질 수 있습니다. 이를 명확히 하기 위해 예제를 살펴보겠습니다.
다이어그램에 표시된 대로 애플리케이션의 루트에 있는 App
구성 요소에서 일부 데이터를 가져왔다고 가정해 보세요. 이제 Grandchild
구성 요소와 같이 깊게 중첩된 구성 요소가 이 데이터에 액세스해야 하는 경우 일반적으로 Grandchild
에 도달하기 전에 Parent
및 Child
구성 요소를 소품으로 전달합니다. 앱이 성장함에 따라 이는 추악해질 수 있습니다.
또 다른 시각적 표현은 다음과 같습니다.
위의 예에서 Profile
구성 요소에는 사용자 데이터가 필요하지만 중간 구성 요소가 데이터 자체를 사용하지 않더라도 이 데이터는 App
및 Navigation
구성 요소를 먼저 통과해야 합니다. 그럼 어떻게 정리할까요? 이것이 바로 Context API가 유용한 곳입니다.
소품 드릴링:
React.js의 Context API를 사용하면 구성 요소 트리의 각 수준을 통해 소품으로 전달할 필요 없이 구성 요소 간에 데이터를 전달할 수 있습니다. 이는 컨텍스트 개체에서 상태를 정의한 다음 구성 요소 트리의 어느 위치에서나 쉽게 액세스할 수 있는 전역 상태 관리 시스템처럼 작동합니다. 예를 들어 이것을 이해해 봅시다.
다이어그램에서 볼 수 있듯이 여러 구성 요소에서 액세스할 데이터를 저장하는 컨텍스트 개체가 있습니다. 이 데이터는 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
구성 요소 폴더에 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
에서 import해야 합니다. 컨텍스트 객체 내에 기본값을 추가할 수 있지만 지금은 null로 유지합니다.
UserContext.Provider
와 같이 UserContext
사용하여 공급자를 반환하는 UserProvider
만듭니다. 이는 하위 구성 요소를 둘러싸며 값에서 하위 구성 요소에 사용하려는 모든 항목을 전달할 수 있습니다.
fetchUserData
함수는 id
받아들이고 해당 ID를 사용하여 사용자 데이터를 가져옵니다. 그런 다음 응답을 user
상태에 저장합니다.
useEffect
에서 fetchUserData
함수를 호출하므로 페이지 로드 시 함수가 호출되고 user
상태에 데이터가 주입됩니다.
이제 <App />
컴포넌트에서 이 컨텍스트를 사용해 보겠습니다. <UserProvider />
를 사용하여 <NavBar />
구성 요소를 래핑합니다. 다음 코드와 동일합니다.
<UserProvider> <Navbar /> </UserProvider>
<Profile />
구성 요소에서 user
상태를 사용해 보겠습니다. 이를 위해 useContext
후크를 사용합니다. 이는 UserContext
사용하고 user
상태 및 fetchUserData
함수와 같이 UserProvider
에 전달한 값을 제공합니다. <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
있으면 사용자 이름을 표시하고, 그렇지 않으면 로그인 메시지만 표시합니다. 이제 출력이 표시되면 navbar 구성 요소에 사용자 이름이 있어야 합니다. 이것이 모든 구성 요소의 컨텍스트에 있는 모든 상태를 직접 사용할 수 있는 방법입니다. 이 상태를 사용하는 구성 요소는 <Provider />
내에 래핑되어야 합니다.
여러 컨텍스트를 사용할 수도 있습니다. 다음 예제와 같이 공급자 구성 요소를 다른 공급자 구성 요소 내에 래핑하기만 하면 됩니다.
<ThemeProvider> <UserProvider> <Navbar /> </UserProvider> </ThemeProvider>
위의 예에서는 테마 상태를 관리하는 <ThemeProvider />
사용하고 있습니다.
위의 YouTube 비디오를 시청하면 여러 컨텍스트 공급자를 사용하는 전체 예를 볼 수 있습니다.
여러 구성 요소에서 Context API를 사용할 때 발생하는 한 가지 문제가 있습니다. Context API에서 상태나 값이 변경될 때마다 모든 구성 요소가 변경된 상태를 사용하지 않는 경우에도 해당 특정 컨텍스트를 구독하는 모든 구성 요소를 다시 렌더링합니다. 이 다시 렌더링 문제를 이해하기 위해 컨텍스트를 사용하여 개수 값을 저장하고 표시하는 <Counter />
구성 요소를 만들어 보겠습니다.
다음 예를 확인하세요. 구성 요소 폴더에 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 />
은 count 값을 사용하지도 않지만 다시 렌더링됩니다. 이는 < <CounterButton />
<CountTitle />
의 상위 구성 요소가 count 값을 사용 및 업데이트하고 있으며 이것이 다시 렌더링되기 때문에 발생합니다.
이 동작을 어떻게 최적화할 수 있습니까? 답은 memo
입니다. React memo
하면 props가 변경되지 않은 구성 요소를 다시 렌더링하는 것을 건너뛸 수 있습니다. <CountTitle />
구성 요소 뒤에 다음 줄을 추가해 보겠습니다.
const MemoizedCountTitle = React.memo(CountTitle)
이제 <CountTitle />
/> 구성 요소를 렌더링하는 <CounterButton />
구성 요소에서 다음 코드와 같이 <CountTitle />
<MemoizedCountTitle />
로 바꿉니다.
<> <MemoizedCountTitle /> <CountDisplay /> <button onClick={() => setCount(count + 1)}>Increase</button> </>
이제 개수를 늘리고 로그를 확인하면 <CountTitle />
구성 요소가 더 이상 렌더링되지 않는 것을 확인할 수 있습니다.
Redux
보다 예측 가능한 상태 전환을 갖춘 복잡한 상태 관리를 위한 상태 관리 라이브러리입니다. Context API는 간단한 상태 관리 및 소품 드릴링 없이 구성 요소 트리를 통해 데이터를 전달하도록 설계되었습니다. 그렇다면 언제 무엇을 선택해야 할까요?
또한 상태 관리에 널리 사용되는 옵션인 라이브러리가 하나 더 있습니다. 반응 반동 .
React Recoil 에 대해 더 자세히 알고 싶으시면 댓글로 알려주세요. 피드백을 바탕으로 이 주제에 대한 심층적인 튜토리얼을 만들겠습니다.
React.js
Context API는 여러 구성 요소의 상태를 관리하는 강력하고 효율적인 방법을 제공하여 prop 드릴링 문제를 효과적으로 해결합니다. Context API를 사용하면 코드를 단순화하고, 불필요한 재렌더링을 줄이고, 전반적인 애플리케이션 성능을 향상시킬 수 있습니다.
Context API는 간단한 상태 관리에 이상적이지만 Redux
나 React Recoil
과 같은 다른 상태 관리 라이브러리를 사용하면 더 복잡한 애플리케이션에 도움이 될 수 있습니다. 이러한 도구를 언제, 어떻게 사용하는지 이해하면 유지 관리 및 확장성이 뛰어난 React 애플리케이션을 구축할 수 있습니다.
이 글을 읽어주셔서 감사합니다. 도움이 되셨기를 바랍니다. React, Redux 및 Next.js를 사용하여 프로젝트를 배우고 구축하는 데 관심이 있다면 내 YouTube 채널( CodeBucks )을 방문하세요.
당신이 읽고 싶어할 만한 다른 기사는 다음과 같습니다.
내 개인 블로그를 방문하세요: DevDreaming