React에서 props만으로 데이터를 전달할 때 불편한 점이 많습니다.
부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하려면,
중간 단계에 있는 모든 컴포넌트가 불필요하게 props를 받아야 하는 문제가 발생합니다.
이 문제를 해결하기 위해 React는 Context API라는 기능을 제공합니다.
이 글에서는 Context의 개념부터 사용법, 최적화 방법까지 하나씩 알아보겠습니다.
Context가 필요한 이유
1. Props의 단점: Props Drilling
React에서 컴포넌트 간 데이터를 전달할 때 보통 props를 사용합니다.
하지만 부모 → 자식 → 손자로 props를 계속 전달해야 하는 경우,
중간 단계의 컴포넌트들이 불필요한 props를 받아야 하는 문제가 발생합니다.
이러한 현상을 Props Drilling(프롭스 드릴링)이라고 합니다.
❌ Props Drilling 문제 예제
function App() {
const user = { name: "Alice", age: 25 };
return <Parent user={user} />;
}
function Parent({ user }) {
return <Child user={user} />;
}
function Child({ user }) {
return <GrandChild user={user} />;
}
function GrandChild({ user }) {
return <div>이름: {user.name}, 나이: {user.age}</div>;
}
✔ 문제점
- GrandChild에서 데이터를 사용하려면 App → Parent → Child → GrandChild 순으로 props를 전달해야 합니다.
- Parent와 Child는 user 데이터를 직접 사용하지 않더라도 props를 전달해야 합니다.
- 컴포넌트가 많아질수록 코드가 복잡해지고 유지보수가 어려워집니다.
Context란?
Context는 컴포넌트 간 데이터를 props 없이 전달할 수 있는 방법입니다.
즉, 전역적인 데이터 저장소 역할을 하는 객체입니다.
✔ Context를 사용하면?
- 중간 컴포넌트에서 불필요한 props 전달 없이 필요한 컴포넌트에서 직접 데이터 접근 가능
- 여러 개의 Context를 생성하여 서로 다른 전역 상태를 관리 가능
1. Context 사용법
1) Context 생성하기
먼저, createContext를 사용하여 새로운 Context를 만듭니다.
import { createContext, useContext } from "react";
// Context 생성
export const UserContext = createContext(null);
// Context 사용하기 (useContext 훅 활용)
export const useUser = () => {
return useContext(UserContext);
};
createContext로 전역 상태를 저장할 공간을 만들고, useContext로 값을 가져오는 형태입니다.
2) Provider로 값 전달하기
Context를 생성한 후, Provider를 사용하여 데이터를 공급해야 합니다.
import { UserContext } from "./UserContext";
import { useState } from "react";
const UserProvider = ({ children }) => {
const [user, setUser] = useState({ name: "Alice", age: 25 });
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
export default UserProvider;
이제 UserContext.Provider 안에 있는 모든 하위 컴포넌트는 user 값을 사용할 수 있습니다.
3) Context 사용하기 (useContext 훅 활용)
이제 하위 컴포넌트에서 useContext를 사용하여 Provider에서 제공한 데이터를 직접 가져올 수 있습니다.
import { useUser } from "./UserContext";
function Profile() {
const { user } = useUser();
return <div>이름: {user.name}, 나이: {user.age}</div>;
}
이제 Profile 컴포넌트는 user 데이터를 props 없이 바로 가져와서 사용할 수 있습니다.
Context 성능 최적화 (불필요한 리렌더링 방지)
Context를 사용할 때 잘못 사용하면 이전보다 더 많은 리렌더링이 발생할 수도 있습니다.
특히, Provider의 value에 객체를 직접 전달하면 컴포넌트가 불필요하게 렌더링 될 수 있습니다.
1. Context 사용 시 발생하는 리렌더링 문제
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
✔ 문제점:
- React는 value={{ user, setUser }}를 새로운 객체로 인식
- 리렌더링이 발생할 때마다 value 객체가 새롭게 생성됨
- 불필요한 리렌더링이 발생하여 성능 저하 가능
2. 해결 방법: Context를 2개로 분리 (stateContext & dispatchContext)
stateContext에는 변경될 수 있는 값만 저장하고, dispatchContext에는 변하지 않는 함수를 저장하면 리렌더링을 줄일 수 있습니다.
📝 Context 분리 예제
import { createContext, useContext, useState, useMemo } from "react";
// 상태 저장 (stateContext)
export const UserStateContext = createContext(null);
// 함수 저장 (dispatchContext)
export const UserDispatchContext = createContext(null);
export const UserProvider = ({ children }) => {
const [user, setUser] = useState({ name: "Alice", age: 25 });
// 변경되지 않는 함수들은 useMemo로 감싸서 리렌더링 방지
const memoizedDispatch = useMemo(() => ({ setUser }), []);
return (
<UserStateContext.Provider value={user}>
<UserDispatchContext.Provider value={memoizedDispatch}>
{children}
</UserDispatchContext.Provider>
</UserStateContext.Provider>
);
};
// Context 가져오는 함수
export const useUserState = () => useContext(UserStateContext);
export const useUserDispatch = () => useContext(UserDispatchContext);
3. 최적화된 Context 사용법
이제 컴포넌트에서 stateContext와 dispatchContext를 각각 사용하면 불필요한 리렌더링을 줄일 수 있습니다.
import { useUserState, useUserDispatch } from "./UserContext";
function Profile() {
const user = useUserState();
return <div>이름: {user.name}, 나이: {user.age}</div>;
}
function ChangeProfile() {
const { setUser } = useUserDispatch();
return (
<button onClick={() => setUser({ name: "Bob", age: 30 })}>
프로필 변경
</button>
);
}
- 이제 Profile과 ChangeProfile이 독립적으로 렌더링됩니다.
- user 값이 변경될 때 ChangeProfile만 리렌더링 되고, Profile은 불필요하게 렌더링 되지 않습니다.
마무리
React에서 Props Drilling 문제는 컴포넌트가 많아질수록 코드가 복잡해지고 유지보수가 어려워지는 문제를 발생시킵니다. 이 문제를 해결하기 위해 Context API를 사용하면, 중간 컴포넌트를 거치지 않고 필요한 곳에서 직접 데이터를 가져올 수 있습니다.
하지만 Context API도 무조건 좋은 해결책은 아닙니다.
✔ 전역 상태가 많아지면 관리하기 어려워질 수 있습니다.
✔ Provider의 value 값이 변경될 때마다 불필요한 리렌더링이 발생할 수도 있습니다.
이러한 문제를 방지하려면
✔ Context를 stateContext와 dispatchContext로 나누어 관리하는 것이 효과적입니다.
✔ useMemo를 활용하여 불필요한 렌더링을 최소화하는 것도 중요합니다.
결국, 상황에 따라 Props, Context API, Redux 같은 상태 관리 라이브러리를 적절히 선택하는 것이 가장 중요합니다.
'DEVELOPMENT > React.js' 카테고리의 다른 글
[React] 웹 스토리지 (Web Storage) (1) | 2025.02.16 |
---|---|
[React] 페이지 라우팅 (Routing) (1) | 2025.02.13 |
[React] 최적화 함수 1탄 (useMemo, React.memo, useCallback) (0) | 2025.02.10 |
[React] 이벤트 핸들링 (Event Handling) (5) | 2025.02.03 |
[React] export 모듈 불러오기 에러 (7) | 2024.06.15 |