리액트 앱은 여러 컴포넌트가 트리 계층 구조로 이루어져 있으며
부모 컴포넌트가 자식 컴포넌트에게 데이터를 전달할 때는 props를 사용한다.
하지만 컴포넌트가 많아지고 계층이 깊어지면
최상위 state 1개를 최하위까지 전해주기 위해
사용하지도 않는 중간 컴포넌트들까지 props를 계속 만들어서 전달해야 한다.
이런 상황이 Prop Drilling(프롭 드릴링) 문제다.
Context API를 사용하면
전역적으로 다뤄야 하는 데이터를 여러 컴포넌트끼리 쉽게 공유할 수 있다.
최상위 App 컴포넌트가
“이 데이터 필요하면 가져가~” 하고 방송하면
필요한 컴포넌트가 바로 가져다 쓸 수 있다.
대표적인 전역 데이터 예시는 아래와 같다.
그럼 props는 필요 없어질까?
정답은 아니다.
React 공식문서에서는
단순히 Prop Drilling만 피하려는 목적이라면
Component Composition(컴포넌트 합성) 을 먼저 고려하라고 한다.
Context 사용이 많아지면 재사용성이 떨어질 수 있기 때문이다.
Component Composition 이란?
부모가 자식에게 특정 모양/구성을 미리 조립해서 넘겨주는 방식.
즉, “설계로 문제를 해결”하려는 접근이다.
context는 마지막 선택지에 가깝다.
import { useState } from 'react';
import './App.css';
import Page from './components/Page';
function App() {
const [isDark, setIsDark] = useState(false);
return <Page isDark={isDark} setIsDark={setIsDark} />;
}
export default App;
import React from 'react';
import Content from './Content';
import Footer from './Footer';
import Header from './Header';
const Page = ({ isDark, setIsDark }) => {
return (
<div className="page">
<Header isDark={isDark} />
<Content isDark={isDark} />
<Footer isDark={isDark} setIsDark={setIsDark} />
</div>
);
};
export default Page;
import React from 'react';
const Header = ({ isDark }) => {
return (
<header
className="header"
style={{
backgroundColor: isDark ? 'black' : 'lightgray',
color: isDark ? 'white' : 'black',
}}
>
<h1>Welcome 홍길동!</h1>
</header>
);
};
export default Header;
import React from 'react';
const Content = ({ isDark }) => {
return (
<div
className="content"
style={{
backgroundColor: isDark ? 'black' : 'white',
color: isDark ? 'white' : 'black',
}}
>
<p>홍길동님, 좋은 하루 되세요!</p>
</div>
);
};
export default Content;
import React from 'react';
const Footer = ({ isDark, setIsDark }) => {
const toggleTheme = () => {
setIsDark(!isDark);
};
return (
<footer
className="footer"
style={{
backgroundColor: isDark ? 'black' : 'lightgray',
}}
>
<button className="button" onClick={toggleTheme}>
Dark Mode
</button>
</footer>
);
};
export default Footer;
import { createContext } from 'react';
export const ThemeContext = createContext(null);
기본값이 null인데,
만약 value가 없다면 이 초기값이 들어간다.
import { useState } from 'react';
import './App.css';
import Page from './components/Page';
import { ThemeContext } from './ThemeContext';
function App() {
const [isDark, setIsDark] = useState(false);
return (
<ThemeContext.Provider value={{ isDark, setIsDark }}>
<Page />
</ThemeContext.Provider>
);
}
export default App;
App이 ThemeContext.Provider로 감싸면서
하위 컴포넌트는 props를 거치지 않아도
isDark / setIsDark 에 접근할 수 있게 되었다.
import React from 'react';
import Content from './Content';
import Footer from './Footer';
import Header from './Header';
const Page = () => {
return (
<div className="page">
<Header /> // 더 이상 props 전달이 필요 없음
<Content />
<Footer />
</div>
);
};
export default Page;
import React, { useContext } from 'react';
import { ThemeContext } from '../ThemeContext';
const Header = () => {
const { isDark } = useContext(ThemeContext);
return (
<header
className="header"
style={{
backgroundColor: isDark ? 'black' : 'lightgray',
color: isDark ? 'white' : 'black',
}}
>
<h1>Welcome 홍길동!</h1>
</header>
);
};
export default Header;
Content.jsx / Footer.jsx 도 같은 방식으로 useContext를 적용한다.
Context는 global state store 전용 도구는 아니다.
그래서 Redux / Recoil / Zustand 같은 상태관리 라이브러리가 존재한다.