[React] useContext 사용해보기
useContext란?
useContext는 컴포넌트가 가장 가까운 MyContext.Provider에서 제공하는 context 값을 읽게해주는 Hook이다.
값을 읽기만 하며, context 값을 설정하려면 Provider를 사용해야 한다.
const value = useContext(MyContext);
MyContext는 createContext()로 만든 객체이다.
useContext()는 현재 컴포넌트가 어떤 Provider 안에 위치해 있을 때만 동작한다.
하위 컴포넌트 어디에서든 값을 꺼내 쓸 수 있다는 점이 핵심이다.
왜 사용할까..?
React에서 props로 상태를 전달할 때 종종 이런 구조가 생긴다.
<App>
<Layout>
<Header>
<ThemeToggler />
</Header>
</Layout>
</App>
이 구조에서 <ThemeToggler /> 까지 상태를 전달하려면
App 에서 ThemeToggler로 props를 계속 넘겨야 한다.
이걸 props drilling이라고 하는데, 코드가 복잡해지는 주 요인 중 하나이다.
해당 요인을 해결하기 위해 사용하는 것이 useContext이다.
실습해보기
테마 전환기
많이들 사용하는 다크모드로의 전환을 위한 테마 전환기로 실습을 해보았다.
ThemeContext.tsx
// ThemeContext.tsx
import { createContext, useContext, useState, type ReactNode } from "react";
type Theme = "light" | "dark";
interface ThemeContextType {
theme: Theme;
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
const [theme, setTheme] = useState<Theme>("light");
const toggleTheme = () => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) throw new Error("useTheme must be used within a ThemeProvider");
return context;
};
useTheme()훅에서 context가 undefined일 경우 예외를 던지게 함으로써, ThemeProvider로 감싸지않고 사용할 경우 바로 알 수 있도록 했다.
ThemeToggler.tsx
import { useTheme } from "../../context/ThemeContext";
const ThemeToggler = () => {
const { theme, toggleTheme } = useTheme();
return (
<button className="p-6" onClick={toggleTheme}>
current theme: {theme}
</button>
);
};
export default ThemeToggler;
ThemeBox.tsx
import { useTheme } from "../../context/ThemeContext";
const ThemedBox = () => {
const { theme } = useTheme();
return (
<div
style={{
padding: "1rem",
textAlign: "center",
marginTop: "1rem",
backgroundColor: theme === "light" ? "white" : "black",
color: theme === "light" ? "black" : "white",
}}
>
테마가 적용된 박스입니다.
</div>
);
};
export default ThemedBox;
UseContextPage.tsx
import ThemeToggler from "../components/UseContext/ThemeToggler";
import ThemedBox from "../components/UseContext/ThemedBox";
import { ThemeProvider } from "../context/ThemeContext";
const UseContextPage = () => {
return (
<ThemeProvider>
<div>
<h1>useContext</h1>
<ThemeToggler />
<ThemedBox />
</div>
</ThemeProvider>
);
};
export default UseContextPage;
해당 실습에서는 UseContextPage에 적용을 해두었지만
보통은 앱 전역에서 사용되므로 App.tsx에 적용한다.
current theme 버튼을 누르면 다크/라이트모드가 전환이 되는 것을 확인할 수 있다.
props 없이도 전역 상태를 여러 컴포넌트에서도 공유할 수 있음을 확인할 수 있다.
createContext(undefined)를 사용한 이유
createContext<ThemeContextType | undefined>(undefined) 에 undefined를 왜 써야할까? 라는 궁금증이 생겨 찾아보게되었다.
- Provider 없이 useContext()를 호출해도 오류가 나지 않는다
-> 기본 값이 있으면 오류 없이 작동하지만, 의도한 동작이 아닐 수 있기 때문에!
- 명확하게 에러를 던지기 위함
-> useTheme()내부에 context가 undefined일 경우 명확하게 예외를 발생시켜 실수를 빨리 잡을 수 있다.
정리
- useContext는 props 없이 전역 상태를 쉽게 공유할 수 있는 Hook이다.
- createContext()와 커스텀 훅으로 사용 범위를 명확하게 제한할 수 있다.
- 테마, 로그인 정보, 언어 설정 등 앱 전역에 영향을 주는 상태에 적합하다.
- props drilling 없이 컴포넌트 간 데이터 공유가 가능하다.
전역 상태 관리에 zustand를 주로 사용하고 있었는데
useContext가 어떤 문제를 해결하기 위해 만들어 졌는지 직접 써보며 이해할 수 있었다.
직접 createContext부터 Provider구성, 커스텀 훅 만들기가지 해보니 zustand같은 상태 관리 도구들이 왜 등장했는지도 알 것 같았다.
복잡한 앱에서는 한계가 있을 수 있겠지만 작은 프로젝트나 간단한 전역 설정에는 여전히 유용한 도구라는 걸 위 글을 작성하며 알게 되었다.
공식 문서
useContext – React
The library for web and native user interfaces
react.dev