context는 React 컴포넌트 트리 안에서 데이터를 전역적으로 공유할 수 있도록 고안된 방법이다.
일반적인 React 애플리케이션에서 데이터는 부모로부터 자식에게 props을 통해 전달되지만 애플리케이션 안의 여러 컴포넌트들에게 전해줘야 하는 props의 경우 이 과정이 번거로울 수 있다.
context를 이용하면 트리 단계마다 명시적으로 props을 넘겨주지 않아도 많은 컴포넌트가 이러한 값을 공유하도록 할 수 있다!
context는 주로 전역적으로 필요한 값을 다룰 때 사용하지만 꼭 전역적일 필요는 없다!
따라서 context을 props가 아닌 또 다른 방식으로 컴포넌트 간에 값을 전달하는 방법이라고 접근하는 것이 좋다!
그렇다면 구체적으로 언제 context을 사용하는 것이 좋을까?
현재 로그인한 유저의 정보, 테마, 선호하는 언어 등 여러 컴포넌트에서 데이터를 공유해야 할 때 context을 사용한다.
만약 context을 사용하지 않는다면,
하위 컴포넌트들에게 테마 정보를 공유하기 위해 일일이 props로 전달해줘야 할 것이다.
function App() {
const theme = 'light';
return <Home theme={theme} />;
}
function Home({ theme }) {
return <Header theme={theme} />;
}
function Header({ theme }) {
return <Button theme={theme} />;
}
function Button({ theme }) {
return <Icon theme={theme} />;
}
function Icon({ theme }) {
return <Message theme={theme} />;
}
function Message({ theme }) {
return <div>현재 {theme} 테마를 사용하고 있습니다!</div>;
}
위의 예시에서 최하위 컴포넌트인 Message
에게 테마 정보를 넘겨주기 위해
부모 → 자식 간 props를 넘겨주는 행동을 반복하고 있다.
이러한 코드를 props drilling이라고 한다.
컴포넌트 한두 개 정도 거쳐서 props을 전달하는 거라면 괜찮지만
위의 코드처럼 4개 정도를 거쳐서 전달하게 되면,
props를 전달해주는 시작점이 어디인지 파악하기도 힘들고 너무 불편하다.
따라서 전역적으로 사용되는 theme 정보는 props로 넘겨주기보다는
context API를 사용하는 것이 좋다 😎
import { createContext } from 'react';
const ThemeContext = createContext('system');
context을 사용하기 위해 먼저 리액트 패키지에서
createContext
라는 함수를 불러와 context 객체를 생성한다!
만약 기본 값을 설정하고 싶다면 createContext
의 인자로 넣어주면 된다.
위 코드에서 context의 기본 값은 'system'이다!
기본 값은 어떠한 컴포넌트가 Provider 내부에 있지 않을 때만 적용된다!
function App() {
return (
<ThemeContext.Provider value='light'>
<Home />
</ThemeContext.Provider>
);
}
context 객체에 포함된 react 컴포넌트인 Provider는
value
을 받아서 이 값을 하위에 있는 컴포넌트들에게 전달한다.
Provider은 context를 구독하는 컴포넌트들에게 context의 변화를 알린다.
Provider 하위에 또 다른 Provider를 배치하는 것도 가능하며,
이 경우 하위 Provider의 값이 우선시된다.
또한 value
값을 전달받을 수 있는 하위 컴포넌트의 수에 제한은 없다!
Provider 하위에서 context를 구독하는 모든 컴포넌트는
Provider의 value
가 바뀔 때마다 다시 렌더링 된다.
따라서 불필요한 리렌더링을 방지하기 위해 꼭 필요한 값만 context로 전달해주는 것이 좋다!
또한 useMemo
나 useCallback
과 같은 최적화 도구를 사용할 수도 있다!
function Message() {
const theme = useContext(ThemeContext);
return <div>현재 {theme} 테마를 사용하고 있습니다!</div>;
// 현재 light 테마를 사용하고 있습니다!
}
하위 컴포넌트에서 value
을 사용하기 위해 useContext
Hook을 사용한다.
useContext
의 인자에는 아까 생성한 context 객체를 넣어준다.
이렇게 하면 중간에 여러 컴포넌트를 거쳐 전달하던 props를 지워주어도 되고
Message
컴포넌트에서 바로 테마 값에 접근할 수 있다.
이렇게 context을 사용하여 변경한 전체 코드는 다음과 같다.
import { createContext, useContext } from 'react';
const ThemeContext = createContext('system');
function App() {
return (
<ThemeContext.Provider value='light'>
<Home />
</ThemeContext.Provider>
);
}
function Home() {
return <Header />;
}
function Header() {
return <Button />;
}
function Button() {
return <Icon />;
}
function Icon() {
return <Message />;
}
function Message() {
const theme = useContext(ThemeContext);
return <div>현재 {theme} 테마를 사용하고 있습니다!</div>;
}
이처럼 context는 전역적으로 관리해야 하는 데이터나 다수의 컴포넌트에 걸쳐 전달되어야 하는 값에 대해 효율적으로 사용할 수 있다.
이를 통해 코드의 가독성이 높아지고, 유지보수가 쉬워진다는 장점이 있다!
위의 코드에서는 theme
를 'light'라는 값으로 고정하여 전달하였다.
만약 theme
를 유동적인 값(state)으로 관리하고 싶다면 어떻게 해야 할까?
import { createContext, useState } from 'react';
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const themeState = useState('light');
return (
<ThemeContext.Provider value={themeState}>
{children}
</ThemeContext.Provider>
);
}
그럴 경우엔 위와 같이 Provider 컴포넌트를 새로 만들어주는 것이 좋다!
이때 useState
로 만들어진 상태 값과 setter 함수가 들어있는 배열을
통째로 Provider의 value
로 전달해준다.
function App() {
return (
<ThemeProvider>
<Home />
</ThemeProvider>
);
}
그리고 새롭게 만든 ThemeProvider
의 태그 사이로 자식 컴포넌트를 넣어준다.
이제 이 자식 컴포넌트에서는 themeState
을 불러와 언제든지 사용할 수 있다!
function Message() {
const [theme, setTheme] = useContext(ThemeContext);
return <div>현재 {theme} 테마를 사용하고 있습니다!</div>;
}
최하위 컴포넌트인 Message
에서 다음과 같은 방식으로 themeState
을 사용할 수 있다.
잘못된 정보나 궁금하신 사항이 있다면 언제든 편하게 댓글로 말씀해주세요!
읽어주셔서 감사합니다 ☺️