React Context로 전역 데이터 관리를 해보자!
리액트로 개발을 할 때 컴포넌트간의 데이터 전달은 props를 통해 이루어지게 됩니다. 하지만 최상위 컴포넌트부터 최하위 컴포넌트까지 데이터를 넘겨야 하는 경우 props를 사용하게 되면 어떻게 될까요? 컴포넌트 트리 내 존재하는 모든 하위 컴포넌트에 props를 추가하여 넘겨주어야 하는 번거로운 상황(Prop Drilling)이 생기게 됩니다.
오늘은 이런 불편함을 없애주기 위해 전역 데이터를 관리하는 방법인 리액트 컨텍스트(React Context)에 대해 알아보도록 하겠습니다.
🤔 React Context를 사용하지 않는 경우
리액트 컨텍스트를 사용하지 않을 때 일어나는 Prop Drilling의 예시를 다음과 같이 생각해 볼 수 있습니다.
function App() {
const [username, setUsername] = useState("Tester");
return (
<div>
<Header username={username} />
</div>
);
}
function Header({ username }) {
return (
<div>
<h1>This is Header</h1>
<Navigation username={username} />
</div>
);
}
function Navigation({ username }) {
return (
<nav>
<ul>
<li>Home</li>
<li>About</li>
<li><UserProfile username={username} /></li>
</ul>
</nav>
);
}
function UserProfile({ username }) {
return <div>안녕하세요, {username}님!</div>;
}
username 이라는 값을 UserProfile 컴포넌트에서 사용하기 위해 App에서 전달을 시작합니다. 이 때, 하위 컴포넌트인 Header, Navigation는 props를 사용하지 않음에도 해당 컴포넌트들을 거쳐서 props가 내려오게 됩니다. 현재 코드에서 props를 추가하거나 수정해야 한다면 모든 중간 컴포넌트를 수정해야 하는 번거로움이 발생할 것입니다.
💡 React Context란?
이러한 prop drilling 상황을 만들지 않고 데이터를 전역(global)으로 관리하게 해주는 것이 바로 React Context 입니다. 원하는 컴포넌트에서 사용하기 위해 계속 props를 넘겨주는 방식과 달리, 중간 컴포넌트를 건너뛰어 데이터를 사용할 대상인 컴포넌트에 바로 데이터 전달이 가능합니다. React Context API는 리액트에 내장되어 있으며, 주로 테마(theme)나 언어, 사용자 데이터 등을 설정하기 위해 사용합니다.
✏️ Context 생성해보기
React에서 제공하는 createContext 함수를 사용하여 컨텍스트를 생성할 수 있습니다.
import { createContext } from "react";
const ThemeContext = createContext("dark");
함수 인자로는 디폴트로 저장할 값이 들어갑니다.
🌱 Provider 컴포넌트 사용하기
하위 컴포넌트들이 생성된 전역 데이터에 접근 가능하게 하기 위해서는 컴포넌트들을 Provider로 감싸주어야 합니다. Provider 컴포넌트는 컨텍스트를 구독하는 컴포넌트들에게 데이터(value)를 전달하며, 컨텍스트의 변화를 알려주는 역할을 합니다.
리액트에서 렌더링 시 값을 읽을 때, 현재 구독하고 있는 컴포넌트와 가장 가까운 Provider를 찾아 값을 읽게 됩니다. 이 때, value로 전달하는 값을 변경하게 되면 Provider로 감싸고 있는 자식 컴포넌트들이 전부 리렌더링 되게 됩니다. 따라서 데이터를 쉽게 전달하고 공유하기 위한 목적으로 사용하는 것이 좋습니다.
const [themeName, setThemeName] = useState<ThemeName>("dark");
const toggleTheme = () => {
setThemeName(themeName === "light" ? "dark" : "light");
localStorage.setItem(
"web_theme",
themeName === "light" ? "dark" : "light"
);
};
return(
<ThemeContext.Provider value={{ themeName, toggleTheme }}>
{/* 하위 컴포넌트들 */}
</ThemeContext.Provider>
)
✏️ Context에 접근하기
🌱 useContext
useContext 함수를 이용하여 생성한 컨텍스트의 데이터에 접근하는 방법입니다. 이 방법은 주로 함수형 컴포넌트에서 사용됩니다.
import React, { useContext } from "react";
import { ThemeContext } from "../../context/themeContext";
function ThemeSwitcher() {
const { themeName, toggleTheme } = useContext(ThemeContext);
return (
<>
<button onClick={toggleTheme}>{themeName}</button>
</>
);
}
export default ThemeSwitcher;
useContext의 인자에 import 해 온 컨텍스트를 넘겨 설정된 데이터 값을 가져와 사용할 수 있습니다.
🌱 Consumer
Consumer를 사용하는 방법은 주로 클래스형 컴포넌트에서 사용됩니다.
<ThemeContext.Consumer>
{value => <div>{value}</div>}
</ThemeContext.Consumer>
Provider와 대응하는 Consumer를 이용하여 전역 데이터에 접근하며, 컨텍스트에서 제공하는 값을 구독하고, 자식 함수의 형태로 제공합니다.
❗️ 주의해야 할 점
React Context를 사용하게 되면 해당 컴포넌트가 Context에 의존하게 되기 때문에 해당 Context 없이는 재사용하기 어렵게 됩니다. 따라서 필요한 범위에만 적절히 사용하는 것이 좋습니다.