4.1 useContext란?4.1.1. 컨텍스트 정의4.1.2. Context 종류4.2 useContext 기본 사용법4.2.1. 기존의 contextAPI를 사용하는 방법4.2.2. useContext 사용하는 방법4.2.3. useState 상태값 넘겨주기4.3. Props Drilling4.3.1. 예제 설명 4.3.2. src 폴더 구조4.3.3. 컴포넌트 구조4.3.4. 각 파일에 대한 설명4.3.5. 실행화면4.3.6. Props Drilling의 문제점 4.4 Context API와 useContext Hook을 사용해 props drilling 해결하기4.4.1. src 폴더 구조4.4.2. 컴포넌트 구조4.4.3. 각 파일에 대한 설명4.4.4. 정리
4.1 useContext란?
4.1.1. 컨텍스트 정의
컴포넌트 간의 데이터(state)를 전달하려면 props를 이용하여 전달해야만 했습니다. props를 통한 전달 방법은, 부모요소에서 자식요소로만 진행되며 부모-자식 컴포넌트의 깊이가 깊어질수록 데이터를 사용하지 않는 컴포넌트라도 자식 컴포넌트가 사용한다면 props를 넘겨줘야 하는 번거로움이 있었습니다.

비교적 작은 규모의 프로젝트의 경우 데이터를 넘겨줄 때엔 간단한 작업으로 효율적으로 작업할 수 있습니다. 하지만 규모가 있는 프로젝트일수록 컴포넌트의 깊이가 깊어지고 데이터의 전달이 많아집니다. props가 필요한 자식 컴포넌트가 아니더라도, 그 아래 자식 컴포넌트에게 전달하기 위해 props를 전달해야 하는 불필요하고 반복적인 작업이 이루어지게 됩니다.

이러한 현상을 props가 드릴로 뚫고 들어간다고 표현하여 props drilling이라고 불립니다. 컨텍스트는 리액트 컴포넌트 트리 안에서 전역적이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법이기 때문에 props drilling 현상을 막을 수 있습니다.

전역적으로 데이터를 관리하고 사용할 수 있는 방법이 있으니, props를 넘겨주는 방식을 사용하지 않고 컨텍스트만 쓰면 되겠다! 라고 생각할 수 있습니다. 하지만 규모가 작은 프로젝트라면 props를 사용하는 것이 훨씬 효율적일 수 있으니 효율성을 생각하여 사용하는게 좋습니다.
4.1.2. Context 종류
리액트에서 공식적으로 Context API를 통해 useContext Hook을 사용하기 전에는 의미없는 props drilling을 피하기 위해 Context API가 아닌, 다른 라이브러리를 사용했습니다. MobX, xstate, recoil, zustand 등 상태관리를 위한 여러 라이브러리가 존재하며 Context API 에서 제공하는 기능 외의 기능을 활용해야 하는지 확인하고 효율성을 따져 라이브러리를 선택해야 합니다.
규모가 큰 프로젝트에서 대량의 데이터를 저장하고 사용할 때 Context API 보다 넓은 범용성을 가진 다른 라이브러리가 더 효율적으로 쓰이며, side effect, memoization, data serialization 등의 기능 등을 활용해야 할 때 선택됩니다.
다른 라이브러리가 여러 기능과 범용성에서 이점을 갖게되니 리액트에도
React.createContext
와 useContext
가 등장하게 되었고 활발하게 사용되고 있습니다.4.2 useContext 기본 사용법
기존의 contextAPI를 사용하여 데이터를 전달하는 방법과
useContext
를 사용하는 방법을 나누어 예제로 확인하겠습니다.4.2.1. 기존의 contextAPI를 사용하는 방법
contextAPI 는
Context.Provider
와 Context.Consumer
를 사용하여야 합니다. 데이터를 전달해주고 싶은 컨텍스트를 Context.Provider
로 감싸줍니다. 데이터를 전달받고 싶은 위치에서
Context.Consumer
를 사용하여 데이터를 받을 수 있도록 합니다.App.jsx
Context.Provider
의 속성으로는 전달해주고 싶은 값을 value 를 통해서 전달할 수 있습니다.import Customer1 from "./Customer1"; import Customer2 from "./Customer2"; import { MenuContext } from "./Context"; function App() { return ( <MenuContext.Provider value={{ 아메리카노: "3500", 카페라떼: "4000", }} > <Customer1 /> <Customer2 /> </MenuContext.Provider> ); } export default App;
Context.jsx
Context는 따로 파일을 생성하여 사용해주도록 하였습니다.
createContext
안에는 데이터의 초깃값이 들어갑니다. 단, 이 초기값은 Context.Provider
를 통해 value 값을 설정해주지 않았을 때, 작동합니다.import { createContext } from "react"; export const MenuContext = createContext(null);
Customer1.jsx 와 Customer2.jsx
아래 예제처럼 데이터를 사용할 하위 컴포넌트에서
MenuContext.Consumer
태그로 감싸주어 사용해주어야 합니다./* Customer1.jsx */ import { MenuContext } from "./Context"; export default function Customer1() { return ( <MenuContext.Consumer> {(price) => <div>1번 고객님은 {price.아메리카노}원을 지불합니다. </div>} </MenuContext.Consumer> ); } /* Customer2.jsx */ import { MenuContext } from "./Context"; export default function Customer2() { return ( <MenuContext.Consumer> {(price) => <div>2번 고객님은 {price.카페라떼}원을 지불합니다. </div>} </MenuContext.Consumer> ); }
4.2.2. useContext 사용하는 방법
useContext
를 사용하면 이러한 복잡함이 줄어듭니다.Customer1.jsx 와 Customer2.jsx
useContext(MenuContext)
를 통해 컨텍스트를 바로 호출하고, 컨텍스트 내의 변수를 사용해주기만 하면 됩니다.import { useContext } from "react"; import { MenuContext } from "./Context"; export default function Customer1() { const price = useContext(MenuContext); return <div>1번 고객님은 {price.아메리카노}원을 지불합니다. </div>; } /*--------------------------------------------------------------*/ /* Customer2.jsx */ import { useContext } from "react"; import { MenuContext } from "./Context"; export default function Customer2() { const price = useContext(MenuContext); return <div>2번 고객님은 {price.카페라떼}원을 지불합니다. </div>; }
App.jsx
아래 예제와 같이
Context.Provider
태그로 감싸주지 않고도 컨텍스트를 사용할 수 있습니다.import Customer1 from "./Customer1"; import Customer2 from "./Customer2"; function App() { return ( <> <Customer1 /> <Customer2 /> </> ); } export default App;
Context .jsx
아래의 예시와 같이
createContext
의 초깃값을 바로 설정해주면 됩니다.import { createContext } from "react"; const Menu = { 아메리카노: "3500", 카페라떼: "4000", }; export const MenuContext = createContext(Menu);
4.2.3. useState 상태값 넘겨주기
useContext
를 통해서 useState
의 동적 상태관리 값도 처리해줄 수 있습니다.간단한 아래의 예제를 통해 알아보겠습니다.
Context.jsx
Context.jsx 파일에 새로운 컨텍스트를 만들어줍니다.
import { createContext } from "react"; const Menu = { 아메리카노: "3500", 카페라떼: "4000", }; export const MenuContext = createContext(Menu); export const OpenContext = createContext();
App.jsx
useState
를 통해 새로 상태 값을 생성해줍니다.Context.Provider
의 값으로 isOpen
과 setIsOpen
을 넘겨줍니다./*App.jsx*/ import { useState } from "react"; import Customer1 from "./Customer1"; import Customer2 from "./Customer2"; import Store from "./Store"; import { OpenContext } from "./Context"; function App() { const [isOpen, setIsOpen] = useState(false); return ( <div className="App"> <OpenContext.Provider value={{ isOpen, setIsOpen }}> <Store /> <Customer1 /> <Customer2 /> </OpenContext.Provider> </div> ); } export default App;
Store.jsx
새로 Store.jsx 파일을 만들어줍니다.
가게를 열고 닫는 버튼을 가지며,
useContext
를 통해서 가져온 setIsOpen
으로 버튼 클릭시 isOpen
의 값을 변경할 수 있도록 해줍니다.import { useContext } from "react"; import { OpenContext } from "./Context"; export default function Store() { const { isOpen, setIsOpen } = useContext(OpenContext); const ClickOpen = () => { setIsOpen(true); }; const ClickClose = () => { setIsOpen(false); }; return ( <div> 가게문이 {isOpen ? "열렸습니다" : "닫혔습니다"} <br /> <button onClick={ClickOpen}>가게문 열기</button> <button onClick={ClickClose}>가게문 닫기</button> </div> ); }
4.3. Props Drilling
Props drilling이란 중첩된 여러 계층의 컴포넌트에 props를 전달해 주는 것을 의미합니다. 단계적으로 일일이 props를 넘겨주다보니 해당 props를 사용하지 않는 컴포넌트들에도 데이터가 제공되는 문제점이 존재합니다. 컴포넌트의 깊이가 깊어질수록 루트 컴포넌트에서 최하위 컴포넌트로부터 순차적으로 데이터를 전달해야 하기 때문에 굉장히 비효율적이고 유지보수 또한 어려워질 것입니다.
4.3.1. 예제 설명
아래의 예제는 props drilling의 예시입니다. 컴포넌트는
App
, Main
, KPopList
, Button
으로 총 4개이며, props는 data
, playlist
, setPlaylist
가 객체의 형태로 필요에 따라 각 컴포넌트마다 단계적으로 전달됩니다. 전달받은 props를 이용해서 케이팝 리스트 중 레드벨벳 노래와 남자 아티스트 노래, 여자 아티스트 노래를 출력합니다. 모든 하위 컴포넌트들은 루트 컴포넌트에서 props로 전달된 데이터에 접근할 수 있습니다.4.3.2. src 폴더 구조
src 폴더의 구조는 다음과 같습니다.
├─ src │ │ │ └─ components │ ├─ Button.jsx │ ├─ KPopList.jsx │ └─ Main.jsx │ ├─ App.css ├─ App.jsx └─ index.js
4.3.3. 컴포넌트 구조
컴포넌트는
App
, Main
, KPopList
, Button
으로 총 4개이며, 각 컴포넌트마다 props로 data
, playlist
, setPlaylist
을 선택적으로 전달됩니다. 모든 하위 컴포넌트들은 루트 컴포넌트에서 props로 전달된 데이터에 접근할 수 있습니다. 
4.3.4. 각 파일에 대한 설명
index.js
// index.js import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <App /> </React.StrictMode> );
App.jsx
App 컴포넌트에서 렌더링할 데이터를 저장하고, useState를 이용하여 상태 값과 data의 상태를 변경하는 함수를 설정한 후
data
, playlist
, setPlaylist
를 하위 컴포넌트인 Main
에 전달합니다. // App.jsx import Main from "./components/Main"; import "./App.css"; import { useState } from "react"; export default function App() { const data = [ { title: "Psycho", artist: "Red Velvet", releaseDate: "2019.12.23", gender: "female", }, { title: "Feel My Rhythm", artist: "Red Velvet", releaseDate: "2022.03.21", gender: "female", }, { title: "Beatbox", artist: "NCT DREAM", releaseDate: "2022.05.30", gender: "male", }, { title: "Attention", artist: "NewJeans", releaseDate: "2022.08.01", gender: "female", }, { title: "Rush Hour", artist: "Crush (Feat. j-hope of BTS)", releaseDate: "2022.09.22", gender: "male", }, ]; const [playlist, setPlaylist] = useState(data); return ( <> <h1>K-POP 플레이 리스트</h1> <Main data={data} playlist={playlist} setPlaylist={setPlaylist} /> </> ); }
Main.jsx
Main 컴포넌트는 플레이리스트를 필터링하기 위한 버튼 3개와 KPopList 컴포넌트로 구성됩니다. 하위 컴포넌트인 Button 컴포넌트와 KPopList 컴포넌트는 props를 계속해서 전달받습니다.
//Main.jsx import React from "react"; import Button from "./Button"; import KPopList from "./KPopList"; // data, playlist, setPlaylist를 props로 전달받은 후, 하위 컴포넌트에 props를 전달합니다. function Main({ data, playlist, setPlaylist }) { return ( <> <Button data={data} setPlaylist={setPlaylist}> 💃🏻 레드벨벳 노래 찾기 </Button> <Button data={data} setPlaylist={setPlaylist}> 🎤 남자 아티스트 노래 찾기 </Button> <Button data={data} setPlaylist={setPlaylist}> 🎵 여자 아티스트 노래 찾기 </Button> <KPopList playlist={playlist} /> </> ); } export default Main;
KPopList.jsx
KPopList 컴포넌트에서는 필터링된 플레이리스트를 렌더링합니다.
playlist
를 props로 전달받아서 모든 데이터의 정보를 제목, 아티스트, 발매일 순으로 렌더링합니다. // KPopList.jsx import React from "react"; import { v4 as uuidv4 } from "uuid"; // playlist를 props로 전달받습니다. function KPopList({ playlist }) { return ( <ul> {playlist.map((song) => ( <li key={uuidv4()}> <h3>{song.title}</h3> <strong>{song.artist} </strong> <span>({song.releaseDate})</span> </li> ))} </ul> ); } export default KPopList;
Button.jsx
Button 컴포넌트에서는 props로
data
, setPlaylist
를 전달받아 버튼을 클릭함에 따라 플레이리스트를 필터링하고 playlist
상태 값을 변경합니다. 화면에는 각 버튼에 해당하는 노래의 정보를 렌더링합니다.//Button.jsx import React from "react"; // props로 data, setPlaylist 를 전달받습니다. function Button({ data, setPlaylist, children }) { // 버튼의 내용에 따라 플레이리스트를 필터링합니다. let result = [...data]; if (children === "💃🏻 레드벨벳 노래 찾기") { result = data.filter((song) => song.artist === "Red Velvet"); } if (children === "🎤 남자 아티스트 노래 찾기") { result = data.filter((song) => song.gender === "male"); } if (children === "🎵 여자 아티스트 노래 찾기") { result = data.filter((song) => song.gender === "female"); } const handleClick = () => { setPlaylist(result); }; console.log(data); return <button onClick={handleClick}>{children}</button>; } export default Button;
4.3.5. 실행화면
코드를 실행하면 다음과 같이 나타나는 것을 확인할 수 있습니다.

‘레드벨벳 노래 찾기’ 버튼을 누르면 플레이리스트의 노래 중 레드벨벳 노래만 나타나는 것을 확인할 수 있습니다.

‘남자 아티스트 노래 찾기’ 버튼을 누르면 플레이리스트의 노래 중 남자 아티스트의 노래만 나타나는 것을 확인할 수 있습니다.

‘여자 아티스트 노래 찾기’ 버튼을 누르면 플레이리스트의 노래 중 여자 아티스트의 노래만 나타나는 것을 확인할 수 있습니다.
4.3.6. Props Drilling의 문제점
Props Drilling은 데이터를 사용하지 않는
App
의 직계 자식 컴포넌트 또한 props를 전달받아야 한다는 점입니다. Main
컴포넌트는 자식 컴포넌트에 props를 내려주기만 할 뿐 직접 필요로 하지 않기 때문에 메모리적으로 비효율적입니다. KPopList
컴포넌트와 Button
컴포넌트가 데이터를 직접 사용하는 것이 좋아보입니다. useContext는 전역적으로 데이터를 공유하기 때문에 중간 다리 역할만 하는 컴포넌트를 건너뛰고 데이터가 직접적으로 필요한 컴포넌트에서 바로 사용이 가능하도록 합니다. 따라서 위와 같이 모든 하위 컴포넌트에 props가 전달되는 문제를 피할 수 있습니다.
4.4 Context API와 useContext Hook을 사용해 props drilling 해결하기
Context API와 useContext Hook을 사용하면 컴포넌트마다 의미없는 props를 넘겨주지 않아도 컴포넌트 트리 전체에 특정 데이터를 제공할 수 있습니다. 이전 챕터의 플레이리스트 예제를 Context API와 useContext Hook을 사용하여 다시 만들어 보겠습니다.
4.4.1. src 폴더 구조
src 폴더의 구조는 다음과 같습니다. context.jsx 파일을 생성해 컨텍스트 관련 코드를 분리했습니다.
src │ ├─ components │ ├─ Button.jsx │ ├─ KPopList.jsx │ └─ Main.jsx │ ├─ context │ └─ context.jsx │ ├─ App.css ├─ App.jsx └─ index.js
4.4.2. 컴포넌트 구조
이전 챕터와 동일한 예제를 구현하므로 컴포넌트의 구조는 동일합니다. 단, 컨텍스트 객체 내에 컴포넌트 트리 전체에서 사용할 값 또는 함수를 저장하고 useContext Hook을 사용해 원하는 값이나 함수를 가져오는 것에서 props drilling과 차이가 있습니다.

4.4.3. 각 파일에 대한 설명
context.jsx
먼저, 플레이리스트 데이터를 별도로 관리하기 위해 context.jsx 파일을 생성합니다.
React.createContext
를 사용해 컨텍스트 객체를 생성합니다. 해당 컨텍스트 객체를 구독할 컴포넌트들을 묶기 위한 Provider를 리턴하는 KPopContextProvider 컴포넌트를 생성합니다. KPopContextProvider로 감싸지는 하위 컴포넌트들은 data
, playlist
, setPlaylist
를 컨텍스트 객체를 통해 접근할 수 있게 됩니다.import {createContext, useState} from "react"; const data = [ { title: "Psycho", artist: "Red Velvet", releaseDate: "2019.12.23", gender: "female", }, { title: "Feel My Rhythm", artist: "Red Velvet", releaseDate: "2022.03.21", gender: "female", }, { title: "Beatbox", artist: "NCT DREAM", releaseDate: "2022.05.30", gender: "male", }, { title: "Attention", artist: "New Jeans", releaseDate: "2022.08.01", gender: "female", }, { title: "Rush Hour", artist: "Crush (Feat. j-hope of BTS)", releaseDate: "2022.09.22", gender: "male", }, ]; // createContext()를 사용하여 context 객체를 생성합니다. const KPopContext = createContext(); // Provider 컴포넌트를 리턴하는 KPopContextProvider를 생성합니다. const KPopContextProvider = ({children}) => { const [playlist, setPlaylist] = useState(data); return ( <KPopContext.Provider value={{data, playlist, setPlaylist}}> {children} </KPopContext.Provider> ) }; export {KPopContext, KPopContextProvider};
index.js
App 컴포넌트와 하위 컴포넌트 전체에 데이터를 제공하기 위해 context.jsx에서 생성한 KPopContextProvider 컴포넌트로 App 컴포넌트를 감싸줍니다.
import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import {KPopContextProvider} from "./context/context"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <KPopContextProvider> <App /> </KPopContextProvider> </React.StrictMode> );
App.jsx
Context API를 사용하지 않는다면 props를 전달하기 위해 데이터를 사용하지 않는 App 컴포넌트에서도 props 값을 전달해야 합니다. 하지만 Context API를 사용하면 props drilling 현상이 사라지고, 코드의 가독성이 개선됩니다. 따라서, App 컴포넌트도 props 전달 없이 코드가 깔끔해집니다.
import "./App.css"; import Main from "./components/Main"; function App() { return ( <> <h1>K-POP 플레이 리스트</h1> <Main /> </> ); } export default App;
Main.jsx
Main 컴포넌트는 플레이리스트를 필터링하기 위한 버튼 3개와 KPopList 컴포넌트로 구성됩니다.
import React from "react"; import Button from "./Button"; import KPopList from "./KPopList"; function Main() { return ( <main> <Button>💃🏻 레드벨벳 노래 찾기</Button> <Button>🎤 남자 아티스트 노래 찾기</Button> <Button>🎵 여자 아티스트 노래 찾기</Button> <KPopList /> </main> ); } export default Main;
KPopList.jsx
KPopList 컴포넌트에서는 필터링된 플레이리스트를 렌더링합니다.
useContext
를 사용해 컨텍스트 객체 내의 playlist
상태 값을 가져오고 특정 노래의 제목, 아티스트, 발매일을 렌더링합니다.import React, {useContext} from "react"; import {v4 as uuidv4} from "uuid"; import {KPopContext} from "../context/context"; function KPopList() { const {playlist} = useContext(KPopContext); return ( <ul> {playlist.map((song) => ( <li key={uuidv4()}> <h3>{song.title}</h3> <strong>{song.artist} </strong> <span>({song.releaseDate})</span> </li> ))} </ul> ); } export default KPopList;
Button.jsx
Button 컴포넌트에서는 버튼을 클릭함에 따라 플레이리스트를 필터링하고
playlist
상태 값을 변경합니다. import React, {useContext} from "react"; import {KPopContext} from "../context/context"; function Button({children}) { const {data, setPlaylist} = useContext(KPopContext); // 버튼의 내용에 따라 플레이리스트를 필터링합니다. let result = [...data]; if (children === "💃🏻 레드벨벳 노래 찾기") { result = data.filter((song) => song.artist === "Red Velvet"); } if (children === "🎤 남자 아티스트 노래 찾기") { result = data.filter((song) => song.gender === "male"); } if (children === "🎵 여자 아티스트 노래 찾기") { result = data.filter((song) => song.gender === "female"); } const handleClick = () => { setPlaylist(result); }; return <button onClick={handleClick}>{children}</button>; } export default Button;
실행화면
코드를 실행하면 다음과 같이 나타나는 것을 확인할 수 있습니다.

‘레드벨벳 노래 찾기’ 버튼을 누르면 플레이리스트의 노래 중 레드벨벳 노래만 나타나는 것을 확인할 수 있습니다.

‘남자 아티스트 노래 찾기’ 버튼을 누르면 플레이리스트의 노래 중 남자 아티스트의 노래만 나타나는 것을 확인할 수 있습니다.

‘여자 아티스트 노래 찾기’ 버튼을 누르면 플레이리스트의 노래 중 여자 아티스트의 노래만 나타나는 것을 확인할 수 있습니다.

App.css
위의 예제에서 사용한 CSS 속성은 다음과 같습니다.
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;600&display=swap"); * { font-family: "Noto Sans", sans-serif; } ul { padding: 0; margin: 0; } li { padding: 10px 5px; list-style: none; } li + li { border-top: 1px solid #bdbdbd; } h3 { margin: 0; line-height: 32px; font-size: 20px; } button { border: none; background-color: #dfebff; color: #007aff; padding: 7px 15px; border-radius: 32px; font-size: 15px; font-weight: 700; cursor: pointer; margin: 3px; } strong { font-weight: 500; }
4.4.4. 정리
Context API와 useContext Hook을 사용하면 컴포넌트에서 데이터와 관련된 코드를 분리할 수 있게 됩니다. 그리고 컴포넌트 내에서 컨텍스트 객체의 특정 값을 사용해야 하는 경우 useContext Hook을 사용해 필요한 값만 가져올 수 있으므로 코드의 가독성이 높아집니다.