기존에는 컴포넌트의 props를 통해 부모에서 자식으로 단방향 데이터를 전송
Context : 컴포넌트 트리를 이용해 곧 바로 데이터를 전송하는 방식
즉, props를 통해 계속 원하는 단계까지 보내는 것이 아닌
데이터를 Context에 담아두었다가 원하는 단계로 바로 꺼내주는 것
언제 Context를 사용해야 할까?
여러 개의 Component들이 접근해야 하는 데이터
- 로그인 여부, 로그인 정보, UI테마, 현재 언어등...
기존 코드
function App(props) { return <Toolbar theme="dark"/>; } function Toolbar(props) { return ( <div> <ThemedButton theme={props.theme}/> </div> ); } function ThemedButton(props) { return <Button theme={props.theme}/>; }App에서 테마를 입력하고, Toolbar컴포넌트가 props로 받아 다시 ThemedButton컴포넌트로 전달후
ThemedButton이 버튼의 속성을 받는다
Context 사용
//컨텍스트는 데이터를 매번 컴포넌트를 통해 전달할 필요 없이 컴포넌트 트리로 곧바로 전달하게 해준다 // 여기에서는 현재 테마를 위한 컨텍스트를 생성하며, 기본값은 'light'이다 const ThemeContext = React.createContext('light'); // ----> Context 생성 // Provider를 사용하여 하위 컴포넌트들에게 현재 테마 데이터를 전달한다 // 모든 하위 컴포넌트들은 컴포넌트 트리 하단에 얼마나 깊이 있는지에 관계없이 데이터를 읽을 수 있다 // 여기에서는 현재 테마값으로 'dark'를 전달 중 function App(props) { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } // 이제 중간에 위치한 컴포넌트는 테마 데이터를 하위 컴포넌트로 전달할 필요가 없다 function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } function ThemedButton(props) { // 리액트는 가장 가까운 상위 테마 Provider를 찾아서 해당되는 값을 사용한다 // 만약 해당되는 Provider가 없을 경우 기본값(여기서는 'light')을 사용한다 // 여기서는 상위 Provider가 있기 때문에 현재 테마의 값은 'dark' return ( <ThemeContext.Consumer> {value => <Button theme={props.theme}/>} </ThemeContext.Consumer> ); }
.Consumer는 React 컨텍스트(Context)의 값을 소비(consume)하고 해당 값에 접근하는 데 사용되는 React 컴포넌트입니다.
React 컨텍스트는 데이터를 상위 컴포넌트에서 하위 컴포넌트로 효율적으로 전달하는 데 사용됩니다. .Consumer 컴포넌트는 이러한 데이터를 소비하고 하위 컴포넌트에서 해당 데이터에 접근할 수 있게 해줍니다.
.Consumer는 다음과 같은 패턴으로 사용됩니다:
<SomeContext.Consumer>
{contextValue => (
// contextValue를 사용하여 원하는 작업 수행
)}
</SomeContext.Consumer>
<SomeContext.Consumer>: 컨텍스트를 소비하는 데 사용되는 컴포넌트입니다. SomeContext는 컨텍스트 객체입니다.{contextValue => ...}: 콜백 함수로, 컨텍스트의 값을 매개변수로 받아와서 사용합니다. 이 부분에서 원하는 동작을 수행할 수 있습니다.예를 들어, 이전 코드에서 ThemedButton 컴포넌트는 ThemeContext.Consumer를 사용하여 value를 가져와서 그 값을 사용하여 버튼을 렌더링하고 있습니다. 이로써 ThemedButton은 상위 컴포넌트에서 제공한 테마 정보를 사용할 수 있게 되는 것입니다.
Context 사용 시 고려사항
- 재사용성이 떨어진다
//Page컴폰넌트는 PageLayout컴포넌트를 렌더링 <Page user={user} avatarSize={avatarSize} /> //PageLayout컴포넌트는 NavigationBar컴포넌트를 렌더링 <PageLayout user={user} avatarSize={avatarSize} /> //NavigationBar컴포넌트는 Link컴포넌트를 렌더링 <NavigationBar user={user} avatarSize={avatarSize} /> //Page컴폰넌트는 PageLayout컴포넌트를 렌더링 <Link href={user.permalink}> <Avatar user={user} size={avatarSize} /> </Link>
Avatar 컴포넌트를 변수에 저장하여 직접 넘기기
function Page(props) { const user = props.user; const userLink = ( // 맨 밑에 있던 컴포넌트 전체를 변수에 저장 <Link href={user.permalink}> <Avatar user={user} size={avatarSize} /> </Link> ); // Page 컴포넌트는 PageLayout 컴포넌트를 렌더링 // 이때 props로 userLink를 함께 전달 return <PageLayout userLink={userLink} />; // 컴포넌트 전체를 넣은 변수를 다른 컴포넌트에게 전달 } // PageLayout 컴포넌트는 NavigationBar 컴포넌트를 랜더링 <PageLayout userLink={...}/> // NavigationBar 컴포넌트는 props로 전달받은 userLink element를 리턴 <NavigationBar userLink={...}/>가장 상위만 정보를 가지고 있으면 된다
또한, 최상위 컴포넌트에게 더 많은 권한이 부여됨
하지만 데이터가 많아질 수록 상위 컴포넌트는 복잡해짐
topBar와 content변수에 하위컴포넌트를 분할해서 넣었다
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} /> ); }하위 컴포넌트의 의존성을 상위 컴포넌트와 분리시킬때 사용
but, 하나의 데이터에 여러 중첩된 컴포넌트가 접근이 필요할 때는 Context를 사용해야 한다(지역정보, UI테마, 캐싱된 데이터)