Context
- 기존의 일반적인 앱 컴포넌트 → props를 통한 데이터 전달
- 자주 사용하는 데이터의 경우 코드가 복잡해지고 사용이 불편함
- 리액트 컴포넌트들 사이에서 데이터를 컴포넌트 트리를 통해 전달

기존 방식
- 여러 컴포넌트에 걸쳐서 자주 사용되는 데이터를 전달 할때 반복적인 코드가 많이 생김

Context 사용
- 데이터를 바로 전달. 데이터를 한 번에서 관리해서 디버깅 하기에 유리
언제 Context 사용?
여러 개의 컴포넌트들이 자주 필요해 하는 데이터
- 로그인 여부, 로그인 정보, UI테마, 현재 언어 등
- props를 통해서 넘겨주면 계속 자식 컴포넌트로 내려가게 됨
function App(props){
return <Toolbar theme="dark" />;
}
function Toolbar(props){
// ThemedButton 컴포넌트에 theme을 넘겨주기 위해서 'theme' prop을 가져야 함
return (
<div>
<ThemeButton theme={props.theme}/>
</div>
);
}
function ThemedButton(props){
return <Button theme={props.theme}/>;
}
// 컨텍스트는 데이터를 매번 컴포넌트를 통해 전달할 필요 없이 컴포넌트 트리로 곧바로 전달
const ThemeContext = React.createContext('light');
// Provider를 사용해서 하위 컴포넌트들에게 현재 테마 데이터 전달
// 테마값으로 dark를 전달
function App(props){
return(
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
// Toolbar는 테마 데이터를 하위 컴포넌트로 전달할 필요가 없음
function Toolbar(props){
return (
<div>
<ThemeButton/>
</div>
);
}
function ThemedButton(props){
// 가장 가까운 상위 테마 Provider를 찾아서 해당되는 값 사용
// Provider가 없을 경우에는 기본값 사용
return (
<ThemeContext.Consumer>
{value => <Button theme={value} />}
</ThemeContext.Consumer>
);
}
여러 컴포넌트에서 계속해서 데이터 접근이 일어날 수 있는 데이터는 context 사용이 좋음
Context를 사용하기 전에 고려할 점
- 컴포넌트와 context가 연동되면 재사용성이 떨어짐
- 다른 레벨의 많은 컴포넌트가 데이터를 사용하는게 아니면 props를 사용하는 기존 방식이 더 나음
// Page 컴포넌트는 PageLayout 렌더링
<Page user={user} avatarSize={avatarSize}/>
// PageLayout 컴포넌트는 NavigationBar 렌더링
<PageLayout user={user} avatarSize={avatarSize}/>
// NavigationBar 컴포넌트는 Link 렌더링
<NavigationBar user={user} avatarSize={avatarSize}/>
// Avatar컴포넌트를 렌더링
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize}/>
</Link>
context를 사용하지 않고 데이터를 전달하는 법
element variable 형태로 Avatar 컴포넌트를 변수에 저장
function Page(props){
const user = props.user;
const userLink = (
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize}/>
</Link>
);
// Page 컴포넌트는 props로 userLink를 함께 전달
return <PageLayout userLink={userLink}/>;
}
// NavigationBar 컴포넌트 렌더링
<PageLayout userLink={...}/>
// props로 전달받은 userLink element를 리턴
<NavigationBar userLink={...}/>
이렇게 하면 Page 컴포넌트만 Avatar 컴포넌트에서 필요로 하는 user와 size를 알고 있으면 됨
- 중간에 있는 컴포넌트에 props 전달X
- 코드가 더 간결해짐
- 최상위 컴포넌트에 많은 권한이 부여됨
데이터가 많아질수록 상위 컴포넌트에 몰려서 상위 컴포넌트는 점점 더 복잡해지고 하위 컴포넌트는 너무 유연해짐
function Page(props){
const user = props.user;
const topBar = (
<NavigationBar>
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize}/>
</Link>
</NavigationBar>
);
const content = <Feed user={user} />;
return (
<PageLayout
topBar={topBar}
content={content}
/>
);
}
하위 컴포넌트를 여러 개의 변수로 나누어 전달
- 하위 컴포넌트의 의존성을 상위 컴포넌트와 분리할 필요가 있는 경우 적합
렌더링 전에 하위 컴포넌트가 상위 컴포넌트와 통신해야 하는 경우 렌더 props를 사용하여 처리할 수 있음
하나의 데이터에 다양한 레벨의 중첩된 컴포넌트가 접근할 때
→ 이러한 방식 사용X, context 사용
→ context는 해당 데이터와 데이터의 변경 사항을 모두 하위 컴포넌트들에게 브로드캐스트해줌.
context를 사용하는 것이 적합한 데이터 예
- 현재 지역정보, UI테마, 캐싱된 데이터 등
Context API
React.createContext - Context 생성
const MyContext = React.createContext(기본값);
함수의 파라미터 값으로 기본값을 넣어준다.
- 리액트에서 렌더링이 일어날 때 context 객체를 구독하는 하위 컴포넌트가 나오면 현재 context의 값을 가장 가까이에 있는 상위 레벨의 provider로부터 받아오게 된다.
- 상위 레벨이 매칭되는 Provider가 없다면 기본값 사용됨
- 기본값으로 Undefined → 기본값이 사용되지 않음
Context.Proivder
데이터를 제공해주는 컴포넌트
모든 context 객체는 Provider라는 리액트 컴포넌트를 가지고 있음
context.Provider 컴포넌트로 하위 컴포넌트들을 감싸주면 모든 하위 컴포넌트들이 해당 컨텍스트의 데이터에 접근할 수 있게 됨
<MyContext.Provider value={/* some value */}>
value라는 prop은 Provider 컴포넌트 하위에 있는 컴포넌트들에게 전달됨
하위 컴포넌트들이 이 값을 사용하게 되는데 하위 컴포넌트들이 데이터를 소비 → consumer 컴포넌트
- consumer 컴포넌트는 context값의 변화를 지켜보다가 만약 값이 변경되면 재렌더링됨
- 하나의 provider 컴포넌트는 여러 개의 consumer 컴포넌트와 연결될 수 있음
- 여러개의 provider 컴포넌트는 중첩되어 사용될 수 있음.
provider 컴포넌트로 감싸진 모든 consumer 컴포넌트는 provider의 value prop이 바뀔 때마다 재렌더링
- 값이 변경되었을 때 상위 컴포넌트가 업데이트 대상이 아니어도 하위 컴포넌트가 context를 사용한다면 하위 컴포넌트에서 업데이트가 일어남.
Provider value에서 주의해야 할 사항
Provider 컴포넌트가 재렌더링될 때마다 모든 하위 consumer 컴포넌트가 재렌더링 됨.
예시 코드
function App(props){
return (
<MyContext.Provider value={{ something: 'something'}}>
<Toolbar />
</MyContext.Provider>
);
}
Value prop을 위한 새로운 객체가 매번 새롭게 생성되어서 하위 consumer 컴포넌트가 재렌더링됨.
→ 해결법: value를 직접 넣는 것이 아닌 컴포넌트의 State로 옮기고 해당 State의 값을 넣어주어야 함.
function App(props){
const [value, setValue] = useState({something: 'something'});
return (
<MyContext.Provider value={value}>
<Toolbar />
</MyContext.Provider>
);
}
value를 직접 넣지 않고 State를 선언하고 State의 값을 provider에 넣어줌.
Context.Consumer
<MyContext.Consumer>
{value=> /* 컨텍스트의 값에 따라서 컴포넌트들을 렌더링 */}
</MyContext.Consumer>
컴포넌트의 자식으로 함수가 옴 ← function as a child
- context.consumer로 감싸주면 자식으로 들어간 함수가 현재 context의 value를 받아서 react node로 리턴하게 됨.
- 이때 함수로 전달되는 value는 provider의 value prop과 동일
- 만약 상위 컴포넌트에 provider가 없다면 이 value 파라미터는 createContext를 호출 할 때 넣는 기본값 역
function as a child
// children이라는 prop을 직접 선언하는 방식
<Profile children={name=><p>이름: {name}</p>}/>
// Profile 컴포넌트로 감싸서 children으로 만드는 방식
<Profile>{name => <p>이름: {name}</p>}</Profile>
기본적으로 하위 컴포넌트들을 children이라는 prop으로 전달
- childeren으로 컴포넌트 대신 함수를 사용하여 이 코드처럼 사용
Context.displayName
context 객체는 displayName이라는 문자열 속성을 가짐.
const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';
// 개발자 도구에 "MyDisplayName.Provider"로 표시됨
<MyContext.Provider>
// "MyDisplayName.Consumer"로 표시
<MyContext.Consumer>
useContext()
함수 컴포넌트에서 context를 쉽게 사용할 수 있게 해줌
function MyComponent(props){
const value = useContext(MyContext);
return (
...
)
}
React.createContext 함수 호출로 생성된 context 객체를 인자로 받아서 현재 context의 값을 리턴
useContextHook을 사용하면 context의 값을 다른 방식과 동일하게 컴포넌트 트리상에서 가장 가까운 상위 provider로 받아옴.
context의 값이 변경되면?
- 변경된 값과 함께 useContextHook을 사용하는 컴포넌트가 재렌더링됨
- useContextHook을 사용하는 컴포넌트가 무거운 작업일 경우 별도로 최적화 작업 필요
- 파라미터로 context 객체를 넣어줘야 함
// 잘못된 사용법
useContext(MyContext.Consumer);
useContext(MyContext.Provider);
- Consumer, Provider를 파라미터로 넣으면 안됨.Context
'Develop > FE' 카테고리의 다른 글
| React + PWA + Vite 프로젝트 시작하기 (feat. tailwind) (0) | 2025.04.19 |
|---|---|
| [React] fragments, forward props, 여러 JSX 슬롯 활용법 (1) | 2024.11.29 |
| [React] Lists and Keys & Forms & Lifting State Up (1) | 2024.10.08 |
| [React] Handling Events & Conditional rendering (1) | 2024.10.03 |
| [React] Hooks (0) | 2024.09.27 |