오늘은 내가 최근에 과제를 하면서 겪었던 props drilling 문제와 이를 리액트 Context API로 해결한 경험을 공유하려고 한다.
포켓몬들의 데이터를 컴포넌트 간에 주고 받을 때 'props'를 이용해서 과제를 만들고 있었다. props는 데이터를 컴포넌트간 쉽게 전달할 수 있지만 'props-drilling'이라고 불리는 문제가 발생한다. 'props-drilling'이란 데이터를 전달하기 위해 여러 중간 컴포넌트를 거치는 경우를 말한다. 예를 들어, 부모 컴포넌트에서 자식 컴포넌트까지 데이터를 전달할 때, 그 사이에 있는 모든 컴퓨터가 'props'로 데이터를 받아야 할 때가 있다. 이건 코드가 길어지고 복잡해지는 원인이 될 수 있다.
그래서 오늘은 'props-drilling'으로 이루어진 과제를 Context API를 사용하여 문제를 해결해 나갈 것이다!!
Context API란?
데이터를 중앙에서 관리하고, 필요한 컴포넌트만 그 데이터를 가져다 쓸 수 있게 만들어준다. 이렇게 하면 중간에 있는 컴포넌트들이 불필요하게 데이터를 전달하지 않아도 된다! 간단하게 Context API 사용방법을 말해보자면, 먼저 'React.createContext()'로 컨텍스트를 생성하고, 'Provider'을 사용해 데이터를 컴포넌트 트리에 공급해준다. 그러면 트리 안에 있는 컴포넌트들은 'useContext' 훅을 사용해서 그때그때 필요할 때마다 데이터를 쉽게 가져올 수 있다.
그러면 이제 실습을 해보자!
Context API로 Props Drilling 해결하기
1. PokemonContext.jsx 파일 생성
src 폴더 안에 따로 context 폴더를 만들어서 그안에 'PokemonContext.js' 파일을 생성한다.
src/
├── components/
├── context/
│ └── PokemonContext.js
├── App.js
└── index.js
보통 Context API를 사용할 때는, 별도의 'context' 폴더를 생성해 context파일을 모아두는 것이 일반적이다. 이렇게 하면 프로젝트 내에서 모든 context 파일들을 한 곳에서 관리할 수 있어서 보기 쉽고 정리도 잘된다. 다른 방법은 components 폴더 내에 만드는 것인데 이렇게 하면 context와 컴포넌트가 섞여 있어서 구조가 복잡해질 수 있다. 그래서 프로젝트가 커지면 유지보수가 어려워질 수 있는 단점이 있다.
2. createContext 생성
<PokemonContext.js>
import { createContext } from "react";
const PokemonContext = createContext();
export default PokemonContext;
- 리액트 라이브러리에서 'createContext' 함수를 import 해온다. ('createContext'는 리액트 context를 생성하는 데 사용되는 함수)
- createContext() 를 호출하여 새로운 context 객체를 생성해 PokemonContext라는 상수에 할당한다.
- 생성된 PokemonContext를 export해서 내보낸다. 이렇게 하면 다른 파일에서 이 context를 쉽게 가져와 사용할 수 있다.
3. 'Provider'을 사용해 데이터를 컴포넌트 트리에 공급
<App.jsx>
import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import Dex from "./pages/Dex";
import "./App.css";
import PokemonDetail from "./pages/PokemonDetail";
import PokemonContext from "./context/PokemonContext";
import MOCK_DATA from "./mock";
const App = () => {
return (
<PokemonContext.Provider value={MOCK_DATA}>
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dex" element={<Dex />} />
<Route path="/pokemon/:id" element={<PokemonDetail />} />
</Routes>
</BrowserRouter>
</PokemonContext.Provider>
);
};
export default App;
- 이전에 생성한 PokemonContext와 자식컴포넌트에 전달할 MOCK_DATA를 import해온다.
- PokemonContext.Provider 를 통해 value={MOCK_DATA}를 전달하여 Provider 내부의 모든 자식 컴포넌트들이 이 context에 접근할 수 있게 된다.
4. 'useContext' 훅을 사용해 데이터 가져오기
<PokemonDetail.jsx>
import PokemonContext from "../context/PokemonContext";
const PokemonDetail = () => {
const pokemonData = useContext(PokemonContext);
const pokemon = pokemonData.find((pokemon) => pokemon.id === parseInt(id));
if (!pokemon) {
alert("포켓몬을 찾을 수 없습니다!");
}
return (
<div className="detail-container">
<h1>
{pokemon.korean_name} (No.{pokemon.id})
</h1>
<img src={pokemon.img_url} alt={pokemon.korean_name} />
<p>타입: {pokemon.types.join(", ")}</p>
<span>특징: {pokemon.description}</span>
<button onClick={handleBack}>⬅️ 뒤로가기</button>
</div>
);
};
export default PokemonDetail;
설정을 통해 자식 컴포넌트인 PokemonDetail.jsx는 PokemonContext를 import 받아 useContext 훅을 사용해 데이터를 쉽게 가져올 수 있다. 받아온 데이터를 pokemonData라는 상수로 받아 사용해주면 간단하다!
.
.
.
# 느낀점
이렇게 변경하고 나니 코드가 훨씬 깔끔해졌다. 더 이상 중간 컴포넌트들에 불필요한 props를 전달하지 않게 되었고 자식컴포넌트는 필요한 데이터에 직접 접근할 수 있게 되었다. 또한, 가장 큰 장점은 유지보수가 쉬워졌다는 것이다. 관련 로직을 변경하고 싶을 때, 이제는 Context Provider만 수정하면 되기 때문이다. 물론 주의할 점도 있다. 모든 것을 Context로 관리하려다 보면 컴포넌트의 재사용성이 떨어질 수 있다고 한다. 그래서 전역적으로 필요한 상태만 Context로 관리하고, 나머지는 여전히 props로 전달하는 방식을 택하는 것이 좋다고 생각한다. 결론적으로 Context API는 props drilling 문제를 해결하는 강력한 도구이다. 하지만 모든 상황에 무조건적으로 사용하기 보다는 프로젝트의 구조와 요구사항을 고려해 적절히 사용하는 것이 중요하다고 생각한다!!!!
'개인과제' 카테고리의 다른 글
로그인 기능 구현하기 (0) | 2024.08.27 |
---|---|
리액트로 만든 나만의 포켓몬 도감: 일주일간의 도전과 성장 (4) | 2024.08.26 |
Redux 사용해서 TodoList 만들기! (0) | 2024.08.22 |
올림픽 메달 트레커 만들기! (0) | 2024.08.14 |
나만의 TodoList 만들어보기 (0) | 2024.08.13 |