Context
state 끌어올리기는 값을 공유하는 컴포넌트들이 가까이 있을 때 잘 작동합니다. 떨어져 있으면 힘들어집니다. 트리의 상단 근처에 있는 값이 6단계 아래에서 필요하면, 그 사이의 모든 컴포넌트를 거쳐야 하고, 각 컴포넌트는 자신이 쓰지도 않는 prop을 받아서 전달해야 합니다. 이런 중계 과정을 **prop drilling**이라고 하는데, Compound components에서 마주친 문제이자 Children.map으로도 해결할 수 없던 문제입니다. Context는 이 문제를 푸는 React의 기본 방법입니다. 한 컴포넌트가 값을 제공하면, 그 아래의 어떤 컴포넌트든 직접 그 값을 읽을 수 있습니다. 사이에 몇 개의 계층이 있든 상관없습니다.
데이터를 순간이동시킨다고 생각하면 됩니다. 중간의 컴포넌트들은 그것을 보지도 전달하지도 않으므로, prop 목록은 자신의 일에만 집중할 수 있습니다.
context 만들고 값 제공하기
Context는 세 부분으로 이루어집니다. 만들기, 값 제공하기, 값 읽기입니다. 만드는 것은 한 번만 일어나고, 파일의 최상단에서, 어떤 컴포넌트의 바깥에서 합니다. 보통 export도 함께 하는데, 그 값을 읽는 컴포넌트들이 다른 파일에 있을 수 있기 때문입니다.
import { createContext } from 'react'
export const ThemeContext = createContext()
export default function App() {
return (
<ThemeContext value="light">
<Header />
<Button />
</ThemeContext>
)
}context 객체를 컴포넌트로 렌더링하면, 그 안의 모든 것이 소비자가 됩니다. value prop이 페이로드인데, 여기서는 문자열 "light"이지만, 어떤 JavaScript 값이든 될 수 있습니다. 객체, 배열, 심지어 함수도 가능합니다. 거기에 넣는 것이 아래의 읽는 쪽이 받게 될 것입니다. prop은 반드시 value라고 이름 붙여야 합니다. 이것이 API의 일부입니다.
배치가 중요합니다. provider가 전체 앱을 감싸야 할 필요는 없고, 보통 그렇게 해서는 안 됩니다. 그 값이 필요한 모든 컴포넌트를 포함하는 가장 작은 서브트리 주위에 놓으세요. UI의 한 구석만 테마에 신경 쓴다면, 그 구석의 공통 조상에서 테마를 제공하고 나머지 트리는 빼세요.
Version note
React 19 이전에는 context를 직접 렌더링할 수 없었습니다. <ThemeContext.Provider value="light"> 형태로 렌더링했습니다. React 19에서 context 객체 자체를 provider로 렌더링 가능하게 했습니다. .Provider 형태는 여전히 작동하고 대부분의 기존 코드베이스와 강의 레슨에서 볼 수 있지만, React는 향후 버전에서 이것을 deprecate할 계획이고 마이그레이션용 codemod를 제공하므로, 새 코드에서는 기본 <ThemeContext> 형태를 사용하세요.
useContext로 값 읽기
provider 아래의 어떤 컴포넌트든 useContext로 값을 읽습니다. useContext는 Hooks에서 처음 소개된 hook인데, context 객체를 전달하면 React가 어떤 context를 읽을지 알 수 있습니다. 이것이 context를 export하는 이유입니다.
import { useContext } from 'react'
import { ThemeContext } from './App'
function Header() {
const theme = useContext(ThemeContext)
return (
<header className={`${theme}-theme`}>
<h1>{theme === 'light' ? 'Light' : 'Dark'} Theme</h1>
</header>
)
}useContext(ThemeContext)는 이 컴포넌트 위의 가장 가까운 ThemeContext provider가 현재 가진 것을 반환합니다. 여기서는 문자열 "light"입니다. App과 Header 사이에 prop을 연결할 필요가 없고, 더 큰 트리에서 그들 사이의 컴포넌트는 테마를 언급할 필요가 없습니다. 한 앱은 여러 context를 동시에 가질 수 있습니다. 각각은 따로 만들어지고, 각각의 객체를 useContext에 전달해서 읽습니다.
살아있게 만들기: state와 context
하드코딩된 value="light"는 절대 바뀌지 않습니다. context를 유용하게 만드는 패턴은 state와 페어링하는 것입니다. state가 값을 소유하고 업데이트하고, context가 그것을 전달합니다. 현재 값과 그것을 바꾸는 함수를 모두 가진 객체를 전달하면, 모든 소비자는 provider 아래 어디서든 테마를 읽거나 업데이트할 수 있습니다.
export const ThemeContext = createContext()
export default function App() {
const [theme, setTheme] = useState('light')
function toggleTheme() {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light')
}
return (
<ThemeContext value={{ theme, toggleTheme }}>
<Header />
<Button />
</ThemeContext>
)
}
function Button() {
const { theme, toggleTheme } = useContext(ThemeContext)
return (
<button className={`${theme}-theme`} onClick={toggleTheme}>
Switch theme
</button>
)
}버튼을 클릭하면, toggleTheme이 실행되고, App의 state가 업데이트되고, App이 다시 렌더링되고, provider가 새 객체를 아래로 전달합니다. context를 읽는 모든 컴포넌트는 새 값으로 다시 렌더링됩니다. 소비자는 필요한 속성만 구조 분해합니다.
이 범위 지정 트릭은 작은 규모에서도 작동합니다. 드롭다운 메뉴 같은 컴포넌트는 자신의 자식 주위에 provider를 렌더링하고, 그 안의 부분들에게 페이지의 나머지로 새지 않는 공유 state를 줍니다. 다음은 Compound components의 메뉴를 완성한 것입니다:
const MenuContext = createContext()
function Menu({ children }) {
const [open, setOpen] = useState(false)
const toggle = () => setOpen(prevOpen => !prevOpen)
return <MenuContext value={{ open, toggle }}>{children}</MenuContext>
}
function MenuButton({ children }) {
const { toggle } = useContext(MenuContext)
return <button onClick={toggle}>{children}</button>
}Menu는 open을 소유하고 그것과 toggle 둘 다를 context에 넣습니다. MenuButton은 useContext로 toggle을 가져오고, 같은 방식으로 작성된 MenuDropdown은 open을 읽어서 렌더링할지 말지 결정합니다. 그 사이에 중계하는 것이 없으므로, 호출하는 쪽에서 버튼을 아무리 많은 레이아웃 div로 감싸도 메뉴는 계속 작동합니다.
Context는 상단 근처의 한 컴포넌트가 "여기 값이 있다"고 말할 수 있게 하고, 아래의 어떤 컴포넌트든 useContext로 직접 집어들 수 있게 합니다. 중간의 컴포넌트들은 그것을 건드리지 않습니다.
State와 페어링하면 값도 바뀔 수 있습니다. 값과 업데이트 함수를 provider에 함께 유지하면, 모든 소비자는 어디서든 그것을 읽거나 바꿀 수 있습니다.
다음: Render props와 headless 컴포넌트. 컴포넌트가 동작을 제공하고 마크업은 당신이 공급합니다.

