Context란?
기존에는 컴포넌트의 props를 통하여 단 방향 데이터 전달되었다.
여러 컴포넌트를 거쳐 자주 사용되는 데이터의 경우 코드의 복잡성과 불편함을 야기한다.
이를 개선하고자 Context를 사용한다.
Component Tree를 통해 직접적으로 전달하여 위 문제점을 해결
언제 Context를 사용해야 할까?
여러 개의 Component들이 접근해야 하는 데이터
ex) 로그인 여부, 로그인 정보, UI테마, 현재 언어 등...
Context 적용 전
function App(props) {
return <Toolbar theme="dark" />;
}
function Toolbar(props) {
// 이 Toolbar 컴포넌트는 ThemedButton에 theme를 넘겨주기 위해서 'theme' prop을 가져야만 합니다.
// 현재 테마를 알아야 하는 모든 버튼에 대해서 props로 전달하는 것은 굉장히 비효율적입니다.
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
}
function ThemedButton(props) {
return <Button theme={props.theme} />
}
Context 적용 후
// 컨텍스트는 데이터를 매번 컴포넌트를 통해 전달할 필요 없이 컴포넌트 트리로 곧바로 전달하게 해줍니다.
// 여기에서는 현재 테마를 위한 컨텍스트를 생성하며, 기본값은 'light'입니다.
const ThemeContext = React.createContext('light');
function App(props) {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
// 이제 중간에 위치한 컴포넌트는 테마 데이털르 하위 컴포넌트로 전달할 필요가 없습니다.
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemeButton(props) {
// 리액트는 가장 가까운 상위 테마 Provider를 찾아서 해당되는 값을 사용합니다.
// 만약 해당되는 Provider가 없을 경우 기본값(여기에서는 'light')을 사용합니다.
// 여기에서는 상위 Provider가 있기 때문에 현재 테마의 값은 'dark'가 됩니다.
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} />
// Link컴포넌트는 Avatar컴포넌트를 렌더링
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
컴포넌트 합성 예제
function Page(props) {
const user = props.user;
const userLink = (
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
);
// Page 컴포넌트는 PageLayout 컴포넌트를 렌더링
// 이때 props로 userLink를 함께 전달함
return <PageLayout userLink={userLink} />;
}
// PageLayout 컴포넌트는 NavigationBar 컴포넌트를 렌더링
<PageLayout userLink={...} />
// NavigationBar 컴포넌트는 props로 전달받은 userLink element를 리턴
<NavigationBar userLink={...} />
컴포넌트 합성 예제2
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}
/>
);
}
Context API
Context 생성
만약 상위 레벨에 매칭되는 Provider가 없다면 기본값이 사용 됨
기본 값으로 undefined를 넣으면 기본값이 사용되지 않
const MyContext = React.createContext(기본값);
Context.Provider
Provider 사용
<MyContext.Provider value={/* some value */}>
/*
Provider Component로 감싸진 모든 Consuming Component는
Provider의 value prop이 바뀔 때 마다 재 렌더링 된다.
값이 변경되었을때 상위 컴포넌트가 변경 대상이 아니더라도
하위에 있는 컴포넌트가 Context를 사용한다면
하위 컴포넌트에서는 업데이트가 일어난다.
값의 변화 기준은 Reference의 MDN참조
*/
Provider value에서 주의 사항
// Provider 컴포넌트가 재렌더링될 때마다
// 모든 하위 consumer 컴포넌트가 재렌더링 됨
function App(props) {
return (
<MyContext.Provider value={{ something : 'something'}}>
<ToolBar />
</MyContext.Provider>
);
}
// state를 사용하여 불필요한 재렌더링을 막음
function App(props) {
const [value, setValue] = useState({ something : 'something'});
return (
<MyContext.Provider value={value}>
<ToolBar />
</MyContext.Provider>
);
}
Class.context
provider하위에 있는 클래스 컴포넌트에서 context 데이터에 접근하기 위해 사용
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* MyContext의 값을 이용하여 원하는 작업을 수행 가능 */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* MyContext의 값에 따라서 컴포넌트들을 렌더링 */
}
}
MyClass.contextType = MyContext;
Context.Consumer
// Component의 자식으로 함수가 올 수 있는데 이를 function as a child라 한다.
<MyContext.Consumer>
{value => /* 컨텍스트의 값에 따라서 컴포넌트들을 렌더링 */}
</MyContext.Consumer>
Function as a child
// children이라는 prop을 직접 선언하는 방식
<Profile children={name => <p>이름 : {name}</p>} />
// Profile 컴포넌트로 감싸서 children으로 만드는 방식
<Profile>{name => <p>이름 : {name}</p>}</Profile>
Context.displayName
const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';
// 개발자 도구에 "MyDisplayName.Provider"로 표시됨
<MyContext.Provider />
// 개발자 도구에 "MyDisplay.Consumer"로 표시됨
<MyContext.Consumer />
여러 개의 Context 사용하기
Context.Provider의 중첩
// 테마를 위한 컨텍스트
const ThemeContext = React.createContext('light');
// 로그인한 유저 정보를 담는 UserContext
const UserContext = React.createContext({
name: 'Guest',
});
class App extends React.Component {
render() {
const {signedInUser, theme} = this.props;
// context 초기값을 제공하는 App 컴포넌트
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
function Layout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}
// 여러 context의 값을 받는 컴포넌트
function Content() {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
useContext()
useContext() Hook을 사용한 예시
import { useContext } from "react";
function MyComponent(props) {
const value = useContext(MyComponent);
return (
...
)
}
// 올바른 사용법
useContext(MyContext);
// 잘못된 사용법
useContext(MyContext.Consumer);
useContext(MyContext.Provider);
Reference
https://ko.legacy.reactjs.org/docs/context.html#gatsby-focus-wrapper
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
'FrontEnd > React' 카테고리의 다른 글
Mini Project (0) | 2024.05.21 |
---|---|
Styling (0) | 2024.05.08 |
Composition vs Inheritacne (0) | 2024.04.29 |
Lifting State Up (0) | 2024.04.24 |
Forms (0) | 2024.04.23 |