React Query에 이어 React Context API로 상태 관리하는 법을 공부해보려 한다
<React Query로 상태 관리하기>
https://tiny-immj.tistory.com/118
React Query로 서버 상태 관리하기 : 데이터 패칭과 캐싱
※ 체크리스트① React Query의 개념과 필요성② React Query의 주요 기능(데이터 패칭, 캐싱, 뮤테이션) 익히기③ React Query를 활용한 서버 상태 관리 방법의 실습 1. React Query 1-1. React Query의 정의 R
tiny-immj.tistory.com
※체크 리스트
① React Context API를 상태 관리 목적으로 사용하는 방법의 이해하기
② Props Drilling 문제를 해결하는 Context API의 기본 개념 학습
③ Context API를 사용해 전역 상태 관리와 컴포넌트 간 데이터 공유를 구현하기
1. Context API
1-1. Context API의 정의
React Context API는 컴포넌트 계층 구조를 통해 데이터를 전달할 때,
Props Drilling 문제를 해결하기 위한 전역 상태 관리 도구이다
1-2. Context API의 주요 구성 요소
▶ Context : 전역상태를 관리하는 객체를 생성
▶ Provider : 데이터를 하위 컴포넌트에 전달
▶ Consumer : 데이터를 소비하는 컴포넌트
1-3. 동작 방식
Provider에서 데이터를 제공하고, 하위 컴포넌트에서 Consumer 또는 useContext Hook으로 데이터를 소비한다
1-4. 장점
● Props Drilling 없이 컴포넌트 간 데이터를 공유할 수 있음
● React의 내장 기능으로 추가 설치가 필요하지 않음
※ Props Drilling 이게 도대체 뭔데?
1) 정의
React에서 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달해야 할 때, 그 데이터를 직접적으로 사용하지 않는 중간 컴포넌트에도 props를 통해 전달하는 과정을 말한다
→ 데이터가 최종적으로 필요한 컴포넌트까지 전달하기 위해 중간 컴포넌트를 거쳐가야 하는 경우 발생한다
import React from 'react';
// 최상위 App 컴포넌트에서 데이터 생성
function App() {
const user = { name: 'Alice', age: 25 };
return (
<div>
<h1>Props Drilling Ex</h1>
<Parent user={user} />
</div>
)
}
// 중간 Parent 컴포넌트
function Parent({ user }) {
return(
<div>
<h2>Parent Component</h2>
<Child user={user} />
</div>
);
}
// 중간 Child 컴포넌트
function Child({ user }) {
return(
<div>
<h3>Child Component</h3>
<UserDetail user={user} />
</div>
);
}
// 실제로 데이터를 사용하는 UserDetail 컴포넌트
function UserDetail({ user }) {
return(
<div>
<h4>User Detail</h4>
<p>Name: {user.name}</p>
<p>Age: {user.age}</p>
</div>
);
}
export default App;
2) 문제점
① 코드 복잡성 증가 :
중간 컴포넌트가 많을수록 props 전달 코드가 중복되고 관리가 어려워진다
② 유지보수 어려움 :
구조가 변경되거나 새로운 데이터를 추가하면 여러 컴포넌트를 수정해야 할 수 있다
③ 불필요한 렌더링 :
중간 컴포넌트가 불필요하게 렌더링될 가능성이 있다
3) 문제를 해결하는 방법
☞ Context API 사용해 계층 구조에 상관없이 데이터를 필요한 컴포넌트에 직접 전달한다
☞ Redux, Zustand 같은 상태 관리 라이브러리를 사용해 전역 상태로 데이터를 관리하여 컴포넌트 간 데이터 전달 문제를 해결한다
☞ React Query 등 데이터 fetching 라이브러리를 사용해 상태 관리를 간소화한다
4) Context API와 Props Drilling의 차이점
Props Drilling은 데이터를 필요 없는 컴포넌트까지 전달해야 하지만,
Context API는 데이터를 필요한 곳에서 직접 접근할 수 있다
2. Context API를 사용한 상태 관리 설정
▶ 순서
① CounterContext를 생성하여 데이터를 공유할 준비를 한다
② CounterProvider로 상태를 관리하고 하위 컴포넌트에 제공할 데이터와 함수를 설정한다
③ Root에서 Provider로 앱 전체를 감싼다
④ 하위 컴포넌트에서 useContext를 사용해 데이터와 함수를 쉽게 가져와 사용할 수 있다
→ 이 구조를 통해 React의 컴포넌트 트리 어디서든 상태에 접근할 수 있는 효율적인 상태 관리가 가능하다
2-1. 진행
1) Context 생성
import { createContext } from 'react';
const CounterContext = createContext();
export default CounterContext;
→ 여기서는 CounterContext를 생성하여, 상태를 관리하고 제공할 준비를 한다
● createContext : React의 내장 함수로, Context를 생성한다. Context는 컴포넌트 트리의 어떤 컴포넌트에서도 접근할 수 있는 전역적인 상태를 관리하기 위해 사용된다.
● CounterContext : 해당 객체는 Provider와 Consumer를 제공한다
● Provider : 데이터를 제공하는 역할
● Consumer : 데이터를 구독하여 읽는 역할
2) Provider로 상태 관리
import React, { useState } from 'react';
import CounterContext from './CounterContext';
function CounterProvider({ children }) {
const [count, setCount] = useState(0);
const increment = () => setCount((prev) => prev + 1);
const decrement = () => setCount((prev) => prev - 1);
return (
<CounterContext.Provider value={{ count, increment, decrement }}>
{children}
</CounterContext.Provider>
);
}
export default CounterProvider;
→ Provider 컴포넌트를 사용해 상태와 업데이트 함수를 공유
● useState로 상태 관리하기 :
useState(0)은 상태 변수 count를 초기값 0으로 선언한다
setCount는 count 값을 변경하기 위해 사용하는 함수이다
● increment 함수 : count를 1 증가시킨다
● decrement 함수 : count를 1 감소시킨다
● CounterContext.Provider의 value 속성 : 하위 컴포넌트에게 제공할 데이터와 메소드를 지정한다
{ count, increment, decrement }는 현재 상태와 이를 조작하는 함수를 하위 컴포넌트들이 사용할 수 있도록 전달한다
● {children} : 이 Provider는 children으로 전달받은 컴포넌트를 감싸 상태를 제공하며, 감싸진 모든 하위 컴포넌트는 value에 접근할 수 있다
3) Provider로 감싸기
import React from 'react';
import CounterProvider from './CounterProvider';
import App from './App';
function Root() {
return (
<CounterProvider>
<App />
</CounterProvider>
);
}
export default Root;
→ 최상위 컴포넌트를 Provider로 감싸 데이터 공유
● CounterProvider :
App 컴포넌트를 CounterProvider로 감싸서 Context를 사용할 수 있는 환경을 설정한다
이제 App와 하위 컴포넌트는 CounterProvider가 제공하는 value(count, increment, decrement)를 사용할 수 있다
● 루트 컴포넌트 구조 :
루트 컴포넌트는 전체 애플리케이션에서 상태를 관리할 준비를 하며 Provider를 통해 모든 하위 컴포넌트에 상태와 상태 변경 함수를 전달한다
2-2. Context 사용 예시
App 컴포넌트나 하위 컴포넌트에서 Context를 사용하는 방법
import React, { useContext } from 'react';
import CounterContext from './CounterContext';
function Counter() {
const { count, increment, decrement } = useContext(CounterContext);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
export default Counter;
● useContext(CounterContext)를 사용해 Provider가 제공하는 데이터를 쉽게 가져온다
3. Context 데이터 소비하기
3-1. 두 가지 방식으로 수행 가능
1) useContext Hook 사용 방식 (현대적 방식)
import React, { useContext } from 'react';
import CounterContext from './CounterContext';
function Counter() {
const { count, increment, decrement } = useContext(CounterContext);
return (
<div>
<p>Count : {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
export default Counter;
① 단계별 설명
useContext로 데이터 가져오기 → 데이터 표시 및 동작 연결
② 장점 :
코드가 간결하고 직관적임
여러 Context를 사용할 때도 간편하게 데이터를 가져올 수 있음
함수형 컴포넌트에서 React Hook과 함께 사용할 수 있음
2) Consumer 컴포넌트 사용 방식 (예전의 방식)
import React from 'react';
import CounterContext from './CounterContext';
function Counter() {
return (
<CounterContext.Consumer>
{({ count, increment, decrement }) => (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
)}
</CounterContext.Consumer>
);
}
export default Counter;
① 단계별 설명 :
Consumer로 데이터 가져오기 → 데이터 표시 및 동작 연결
② 단점 :
코드가 길고 읽기 어렵다
중첩된 구조로 가독성이 떨어질 수 있다
함수형 컴포넌트와 Hook이 없는 환경에서 주로 사용되던 방식이다
3-2. Context API 상태 관리와 데이터 소비의 차이점
구분 | 상태 관리 설정 | 데이터 소비하기 |
역할 | Context를 생성하고 Provider로 데이터를 제공 | useContext를 사용해 데이터를 읽고 활용 |
위치 | 일반적으로 Provider를 정의한 컴포넌트 | Provider로 감싸진 하위 컴포넌트 |
코드 구성 | createContext, Provider, 상태 관리 로직 포함 | useContext를 통해 데이터와 함수에 접근 |
주요 함수/Hook | createContext, useState | useContext |
사용 목적 | 데이터와 상태를 하위 컴포넌트로 전달할 준비 | 데이터를 읽고 화면에 표시하거나 로직에 사용, Context 상태 관리 설정에서 준비한 데이터를 실제로 사용하는 단계 |
4. Context API로 상태 관리 구현
이 전에 설명한 Context 상태 관리 설정과 Context 데이터 소비하기에서 배운 내용을 응용하여 여러 개의 Context를 동시에 관리하고 사용하는 방법을 보여준다
4-1. Context 상태 관리 단계들과 차이점
1) Context 상태 관리 설정과 차이점
● Context 상태 관리 설정은 단일 Context를 생성하고 Provider를 사용해 데이터를 제공하는 기본 구조를 설정하는데 중점을 둔다
● Context API로 상태 관리 구현은 다중 context를 사용하며, 각각 Context에 독립적인 상태와 데이터를 할당해 관리한다
2) Context 데이터 소비하기와의 차이점
● Context 데이터 소비하기에서는 단일 Context에서 데이터를 읽고 사용하는 방식에 초점을 맞췄다
● 이 코드는 여러 Context를 한 컴포넌트에서 동시에 소비하며, 각 Context에서 제공하는 데이터를 조합해 사용한다
4-2. 코드 구조 분석
1단계 : 다중 Context 관리
const ThemeContext = createContext();
const CounterContext = createContext();
function App() {
return (
<ThemeContext.Provider value={{ theme: 'dark' }}>
<CounterContext.Provider value={{ count: 10 }}>
<ChildComponent />
</CounterContext.Provider>
</ThemeContext.Provider>
)
}
① 여러 Context 생성 :
ThemeContext : 테마 관련 데이터를 관리
CounterContext : 카운터 데이터를 관리
② 중첩된 Provider :
ThemeContext.Provider와 CounterContext.Provider를 중첩하여 각 Context에 데이터를 제공
ChildComponent는 중첩된 두 Provider로부터 데이터를 소비할 수 있음
2단계 : 중첩된 Context 소비
function ChildComponent() {
const { theme } = useContext(ThemeContext);
const { count } = useContext(CounterContext);
return (
<div style={{ color: theme === 'dark' ? 'white' : 'black' }}>
Count: {count}
</div>
);
}
① useContext로 다중 Context 소비 :
useContext(ThemeContext)로 테마 데이터를 읽음
useContext(CounterContext)로 카운터 데이터를 읽음
각 Context의 데이터를 독립적으로 가져와 조합하여 사용
② 데이터 조합 :
테마 데이터를 스타일에 적용하여 배경색이나 텍스트 색상을 변경
카운터 데이터를 화면에 표시
5. 실습 과제
5-1. 과제 구현 목표와 예제
① Context API를 사용하여 사용자 이름과 나이를 관리하고 화면에 표시하세요
② 테마(Context)를 추가하여 배경색을 변경할 수 있는 버튼을 구현하세요
예제 코드
function ThemeToggler() {
const { theme, toggleTheme } = useContext(ThemeContext);
return(
<button onClick={toggleTheme}>
현재 테마: {theme === 'dark' ? '다크' : '라이트'}
</button>
);
}
5-2. 구현
1) 프로젝트 생성
npx create-react-app react-context-api-ex
2) 프로젝트 구성
UserContext.js : 사용자 정보를 위한 Context 생성
ThemeProvider.js : 테마 변경 기능을 위한 Context 생성
UserProvider.js : 사용자 정보 관리와 Provider 제공
App.js : 전체 애플리케이션 구조와 Context Provider 중첩
UserProfile.js : 사용자 정보를 화면에 표시
ThemeToggler.js : 테마를 변경하는 버튼
3) 구현
> UserContext.js
// src/context/UserContext.js
import { createContext } from 'react';
export const UserContext = createContext();
> UserProvider.js
// src/context/UserProvider.js
import React, { useState } from 'react';
import { UserContext } from './UserContext';
function UserProvider({ children }) {
const [user, setUser] = useState({ name: '홍길동', age: 25 });
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}
export default UserProvider;
> ThemeProvider.js
// src/context/ThemeProvider.js
import React, { createContext, useState } from 'react';
export const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export default ThemeProvider;
> App.js
// src/App.js
import React from 'react';
import UserProvider from './context/UserProvider';
import ThemeProvider from './context/ThemeProvider';
import UserProfile from './components/UserProfile';
import ThemeToggler from './components/ThemeToggler';
function App(){
return (
<UserProvider>
<ThemeProvider>
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<UserProfile />
<ThemeToggler />
</div>
</ThemeProvider>
</UserProvider>
);
}
export default App;
> UserProfile.js
// src/components/UserProfile.js
import React, { useContext } from 'react';
import { UserContext } from '../context/UserContext';
import { ThemeContext } from '../context/ThemeProvider';
function UserProfile() {
const { user } = useContext(UserContext);
const { theme } = useContext(ThemeContext);
return (
<div
style={{
background: theme === 'dark' > '#333' : '#fff',
color: theme === 'dark' ? '#fff' : '#000',
padding: '20px',
borderRadius: '8px',
}}
>
<h2>사용자 정보</h2>
<p>이름: {user.name}</p>
<p>나이: {user.age}</p>
</div>
);
}
export default UserProfile;
> ThemeToggler.js
// src/components/ThemeToggler.js
import React, { useContext } from 'react';
import { ThemeContext } from '../context/ThemeProvider';
function ThemeToggler() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{
marginTop: '20px',
padding: '10px 20px',
cursor: 'pointer',
}}
>
현재 테마: {theme === 'dark' ? '다크':'라이트' }
</button>
);
}
export default ThemeToggler;
4) 실행
npm start
※ 정리
Q&A
① Context API와 Redux의 차이점은 무엇인가요?
→ 목적과 구현 방식에 있어 차이점이 있다
Context API는 작은 애플리케이션이나 상태가 간단한 경우 유용하며 구현이 간단하고,
Redux는 큰 애플리케이션에서 복잡한 상태 관리를 효율적으로 처리하기 위해 사용된다
Redux는 상태 관리 로직을 더 명확하게 정의하고 디버깅 도구와 성능 최적화를 제공한다
② Context API는 어떤 규모의 애플리케이션에 적합한가요?
→ 간단한 애플리케이션에서 여러 컴포넌트가 동일한 데이터를 공유해야 할 때 유용하다
☞ Context API는 Props Drilling 문제를 해결하며, 간단한 전역 상태 관리에 유용하다
☞ Provider로 상태를 제공하고 useContext로 데이터를 소비한다
☞ Redux와 달리 복잡한 상태 관리는 적합하지 않을 수 있다
'개발 기록 > front - react' 카테고리의 다른 글
Emotion 라이브러리 - React 프로젝트의 디자인 관리 (0) | 2024.12.30 |
---|---|
React에서 Hook이란? (공부 + 면접 답변용) (3) | 2024.12.18 |
React Query로 서버 상태 관리하기 : 데이터 패칭과 캐싱 (0) | 2024.12.17 |
[React] 함수형 컴포넌트 (1) | 2024.11.27 |
[React] React 컴포넌트 생명주기, 생명주기 메소드 (0) | 2024.11.26 |