이 글은 잘못된 정보를 담고 있다.
아니 무슨 styled components가 class를 통해 ThemeProvider가 제공하는 데이터를 받는다는 뻘글인데 이건 완전히 잘못된 정보다.
styled-components가 생성한 컴포넌트는 고유한 클래스명을 가지며, 이 클래스명은 styled-components가 그냥 스타일을 주입하고 관리하는 방식과 관련이 있을 뿐이다.
ThemeProvider가 제공하는 속성을 상속받는건 그냥 React의 Context API를 사용하는거였다.
즉, ThemeProvider는 React Context API를 사용하여 theme 객체를 하위 컴포넌트 트리에 제공한다.
아니 근데 어떻게 여기저기 컴포넌트에서 저기 멀리 저장된 Provider의 theme 객체를 가져와 쓸 수 있는거지?
Context API는 React에서 전역 상태를 관리하고, 컴포넌트 트리에서 데이터를 공유하는 방법이며 아래와 같은 구성 요소로 이루어져 있다.
아니 근데 React version 19 부터는 Provider가 필요없다...!
공식문서링크
createContext 훅 사용해서 Context 생성하고 Provider로 감싸주고(React version 19부터는 좀 다르지만) useContext나 Consumer 사용해서 저장한 Context의 value를 가져와 쓴다.
이런 기본적인 사용법말고 실제 어떻게 Context객체에 저장된 value를 가져오는지 알아보자.
Context의 value는 React의 Fiber 트리에 저장된다. React는 컴포넌트 트리를 Fiber라는 데이터 구조로 관리하며, 각 Fiber노드는 컴포넌트의 상태, props, 그리고 Context 정보를 가지고 있다.
Provider가 렌더링될 때 value는 해당 Context 객체와 연결되어 Fiber 트리의 일부로 저장된다.
<ThemeContext.Provider value={theme}>
위 코드가 렌더링 될 때 React는 해당 Provider를 나타내는 Fiber 노드에 ThemeContext와 연결된 value를 저장한다. 이 value는 Context 객체의 현재 상태로 간주되며, Fiber 트리의 특정 노드(Provider 노드)와 연결된다.
javascript 관점으로 보자면 value는 javascript 객체로, Provider의 value prop에 전달된다. React는 이 객체의 참조를 Context 객체와 연결하여 이 Context 객체를 Fiber트리에 저장한다.
const ThemeContext = React.createContext(null);
function App() {
const theme = { color: 'blue' };
return (
<ThemeContext.Provider value={theme}>
<Child />
</ThemeContext.Provider>
);
}
위 코드를 보면서 정리하자면
즉 value는 Context 객체에 저장되고, 이 Context 객체는 Provider의 Fiber 노드의 Context 스코프에 저장된다.
처음에 그냥 뇌피셜로 추정하기로는 함수형 컴포넌트니까..뭔가..그냥...클로저에 저장되는데 컴포넌트도 함수니까 자식 컴포넌트도 어찌보면 부모 함수안에 있는 자식함수고
그 자식 함수는 생성될 때 부모함수의 변수들을 기억하니까 부모함수에 저장된 값인 Provider의 value에 접근할 수 있다...
뭐 이런건가? 싶었다.
클로저에 저장해놓고 거기에 접근해서 쓰는건 줄 안거지.
근데 역시 아니었음.
React의 Context API는 클로저를 직접 사용하지 않는다.
Fiber 트리는 React 16에서 도입 된 재조정(reconciliation) 엔진의 핵심 데이터 구조다.
이 글에서 공부했듯 React에서 컴포넌트 트리를 렌더링하고 DOM을 업데이트하기 위해 내부적으로 Fiber 트리를 유지한다.각 Fiber 노드는 컴포넌트, DOM 요소 또는 기타 렌더링 단위를 나타내며 다음과 같은 정보를 포함한다.
Fiber 트리의 특징:
import React, { createContext, useContext } from 'react';
import { createRoot } from 'react-dom/client';
const ThemeContext = createContext(null);
function App() {
const theme = { color: 'blue' };
return (
<ThemeContext.Provider value={theme}>
<Parent />
</ThemeContext.Provider>
);
}
function Parent() {
return <Child />;
}
function Child() {
const theme = useContext(ThemeContext);
return <div>Color: {theme.color}</div>;
}
const root = createRoot(document.getElementById('root'));
root.render(<App />);
위 코드를 Fiber 트리로 시각화 하면 대략 아래와 같다.
// Fiber 트리 (간략화된 구조)
RootFiber
└── App (type: FunctionComponent, props: {})
└── ThemeContext.Provider (type: Context.Provider, props: { value: { color: 'blue' } })
└── Parent (type: FunctionComponent, props: {})
└── Child (type: FunctionComponent, props: {}, dependencies: [ThemeContext])
Child 컴포넌트 노드에서 시작하면서 부모 노드로 올라가며 ThemeContext.Provider를 찾는거다.
그렇게 쭉쭉 올라가다가 Provider 노드를 찾게되면 Provider 노드의 value를 가져오는거지.
실제 useContext 훅의 코드를 보고 싶었지만 그냥 내가 생각하는 javascript로 이루어진 코드가 아니라 C++로 작성된 Fiber 아키텍쳐와 Javascript 런타임이 복합적으로 작동하는 매우 복잡한 시스템이라기에 확인할 순 없었다.
그래서 일단 그록씨에게 확인한 대략적인 의사코드를 붙여넣는다.
일단 이렇게 알고 나중에 좀 더 깊게 알게되면 그 때 더 알아봐야할듯
// Fiber 노드 구조 (간략화)
const FiberNode = {
type: 'ComponentType', // 컴포넌트 타입 (예: ThemeContext.Provider)
props: {}, // props (예: { value: { color: 'blue' } })
return: null, // 부모 노드
child: null, // 자식 노드
sibling: null, // 형제 노드
};
// useContext의 의사 코드
function useContext(Context) {
let fiber = ReactCurrentFiber; // 현재 컴포넌트의 Fiber 노드
while (fiber) {
if (fiber.type === Context.Provider) {
return fiber.props.value; // Provider의 value 반환
}
fiber = fiber.return; // 부모로 이동
}
return Context._currentValue; // 기본값 반환
}