이번 포스팅에서는 Context API라는 개념과 React의 Hook 중 하나인 useContext에 대해 살펴보려고 한다.
React 애플리케이션을 개발하다 보면 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하기 위해 여러 단계의 컴포넌트를 거쳐야 하는 경우가 발생한다.
이렇게 데이터를 사용하지 않는 컴포넌트들을 통해 props를 전달하는 현상을 props drilling이라고 하는데, useContext와 Context API를 활용하면 이러한 문제를 깔끔하게 해결할 수 있다.
이번 포스팅을 통해 Context API와 useContext를 어떻게 활용하는지 자세히 살펴보자 👀
우선 Context API와 useContext 훅에 대해서 간단하게 개념을 정리해보자
Context API
- Context API는 컴포넌트 트리에서 전역적으로 데이터를 공유할 수 있도록 해주는 API
useContext()
- useContext는 Context API를 더 쉽게 사용할 수 있게 해주는 Hook
즉, Context API를 사용해서 데이터를 정의하고 useContext 훅으로 정의된 데이터를 적절하게 사용하는 것이다.
그렇다면 이 두가지 개념을 어떻게 사용할 수 있을까?
우선, 전체 흐름부터 살펴보자.
Context API와 useContext를 사용할 때는 다음 3단계를 거친다고 정리할 수 있다.
- Context 생성 (React.createContext)
- Provider 정의 (Context.Provider)
- Context 소비 (useContext)
이제부터 간단한 예제를 통해 실제로 어떻게 사용되는지 살펴보자
테마 변경 기능이 있는 간단한 웹 페이지를 만들어볼 건데, 컴포넌트 구조는 다음과 같다.
컴포넌트 구조
App
→Contents
→Blog, News
여기서 Contents
는 App
으로부터 props를 전달받는데, 이 props를 Blog, News
로 다시 전달하는 역할만 수행한다.
이처럼 props를 트리의 여러 단계를 거쳐 넘기는 것을 props drilling이라고 한다.
props drilling이 발생하는 코드는 다음과 같다.
Sample Code
export default function App() { const [theme, setTheme] = useState("light"); const changeTheme = () => setTheme(theme === "light" ? "dark" : "light"); return ( <div> <Contents theme={theme} changeTheme={changeTheme} /> </div> ); }
const Contents = ({ theme, changeTheme }) => { return ( <> <Blog theme={theme, changeTheme} /> <News theme={theme, changeTheme} /> </> ); };
const Blog = ({theme, changeTheme}) => { return ( <div style={{ backgroundColor: theme === "light" ? "#fff" : "#333", color: theme === "light" ? "#000" : "white" }}> <h1>블로그</h1> <p>블로그 내용</p> <button onClick={changeTheme}>테마 변경</button> </div> ); };
이전에 언급했던 상황을 코드로 작성해봤다.
최상위 컴포넌트 App
에서 theme이라는 상태변수와 이를 변경하는 로직을 정의하고 있다.
하지만, 실질적으로 이 데이터가 사용되는 컴포넌트는 Blog
, News
이다.
위 구조에서는 Contents 컴포넌트가 theme과 changeTheme를 직접 사용하지 않음에도 하위 컴포넌트로 전달하기 위해 props로 받아야 하는 상황이 발생한다.
이제 Context API를 사용해서 같은 기능을 구현해보자
Sample Code
- ThemeContext.js
// 1. Context 생성 const ThemeContext = createContext(); export default ThemeContext;
- ThemeProvider.jsx
// 2. Provider 정의 (1) import ThemeContext from './ThemeContext'; const ThemeProvider = ({ children }) => { const [theme, setTheme] = useState("light"); const changeTheme = () => setTheme(theme === "light" ? "dark" : "light"); return ( <ThemeContext.Provider value={{ theme, changeTheme }}> {children} </ThemeContext.Provider> ); }; export default ThemeProvider;
- App.jsx
// 2. Provider 정의 (2) export default function App() { return ( <div> <ThemeProvider> <Contents theme={theme} changeTheme={changeTheme} /> </ThemeProvider> </div> ); }
- Blog.jsx
// 3. Context 소비 const Blog = () => { const { theme, changeTheme } = useContext(ThemeContext); return ( <div style={{ backgroundColor: theme === "light" ? "#fff" : "#333", color: theme === "light" ? "#000" : "white" }}> <h1>블로그</h1> <p>블로그 내용</p> <button onClick={changeTheme}>테마 변경</button> </div> ); }; export default Blog;
하나씩 설명하기 위해 코드를 분리해서 작성해봤다.
Context API 사용이 처음이라면 기존보다 많이 복잡해진 것 같다고 생각할 수 있다.
주요 변경점을 하나씩 살펴보자
1. ThemeContext.js
ThemeContext
에서는 앞으로 사용할 context를 React.createContext() 함수를 사용하여 정의하고 있다.이 부분이 바로 Context 생성 단계에 해당한다.
2-1. ThemeProvider.jsx
ThemeProvider
에서는 props로 children를 전달받는다.기존 코드에서 변경점을 찝어보면 Theme상태변수와 이를 변경하는 로직이
ThemeProvider
에서 정의된다는 것이다.그 이유는 children에 해당하는 컴포넌트들이 useContext 훅을 통해 이를 사용할 수 있도록 하기 위함이다.
어떻게 Theme과 changeTheme을 전달할까?
return ( <ThemeContext.Provider value={{ theme, changeTheme }}> {children} </ThemeContext.Provider> );
return문에서 이전에 생성한 ThemeContext로 children을 래핑한다.
이 때, ThemeContext.Provider로 래핑을 수행하고, 위처럼 value 속성에 정의한 것들을 넘겨주면 된다.
그러면 children에 해당되는 컴포넌트들은 value의 값을 사용할 수 있다.
(Context API를 사용하는 규칙이다!)
2-2. App.jsx
다음으로 최상위 컴포넌트인App
에서 ThemeProvider로 컴포넌트들을 래핑한다.return ( <div> <ThemeProvider> <Contents theme={theme} changeTheme={changeTheme} /> </ThemeProvider> </div> );
이렇게 래핑하면 ThemeProvider의 props인 {children}으로
Contents
컴포넌트가 넘어온다.여기서 중요한 것은
Contents
컴포넌트 하위에News
와Blog
컴포넌트가 있다는 것이다.즉, {children}에 해당되는
Contents
,News
,Blog
컴포넌트들은 ThemeProvider가 value 속성으로 제공하는 것들을 사용할 수 있게 된다.이 과정이 Provider 정의 단계에 해당된다.
3. Blog.jsx
마지막으로ThemeProvder
가 value 속성으로 넘겨준 값들을 사용하고자하는 컴포넌트에서 useContext()훅으로 불러오면 된다.const { theme, changeTheme } = useContext(ThemeContext);
이때, useContext의 매개변수로는
ThemeContext
를 넘겨주어 사용하고자하는 Context를 지정해야 한다.구조 분해 할당으로 value에 해당한 값들을 불러와서 사용하면 된다.
Context API를 사용할 때는 몇 가지 고려해야 할 점이 있다.
주의사항
- Provider의 위치는 데이터가 필요한 컴포넌트들을 모두 포함할 수 있는 가장 낮은 위치에 배치하자
- Context의 값이 자주 변경되는 경우 성능에 영향을 줄 수 있으므로 주의하자
- 전역적으로 관리할 필요가 있는 데이터에만 Context를 사용하자
위 주의사항은 한가지 맥락이 있다.
바로 useContext로 Context를 구독하면 해당 Context가 변경될 때마다 구독한 컴포넌트들이 모두 리렌더링된다는 것이다.
이러한 개념을 인지하고 사용하는 것이 중요하다!
이번 포스팅에서는 React의 Context API와 useContext Hook에 대해 알아봤다.
props drilling 문제를 해결하고 전역 상태를 관리하는데 매우 유용한 도구지만, 모든 상황에서 Context를 사용하는 건 적절하지 않을 수 있다.
상황에 맞춰 적절히 사용한다면 코드의 가독성도 높이고 유지보수도 쉽게 만들 수 있을 것이다 👊