React
에서 데이터를 전달하는 기본 방식은 Props
입니다.
부모 컴포넌트가 자식 컴포넌트에 데이터를 전달하는 방식이죠.
그러나, Props
방식은 여러 단계의 컴포넌트를 거쳐 데이터를 전달해야 할 때 상당히 비효율적이 됩니다.
부모-자식 관계의 깊이가 깊어질 때 발생하는 현상을 우리는 Prop Drilling
이라 부르는데 바로 이 문제를 해결하려고 사용하는것입니다.
- 만약 이런 구조의 프로젝트가 있다고 했을때,
- 혹시라도 이렇게 어플리케이션이 커진다면?
위와 같이Prop Drilling
이 일어날것입니다.
- 이럴때 사용할 수 있는것이 바로
Context API
입니다.
Context API
를 통해 우리는 Prop Drilling
를 해결 할 수 있고, 데이터를 전역적으로 관리를 할 수 있습니다.Context API
상태를 직접 관리하지 않고 useState
나 useReducer
를 통해 이루어집니다.
다시 말해, 데이터를 전달하는 역할만 한다는 뜻입니다.
Context API
는 데이터가 변경되면 모든 자식 컴포넌트가 리렌더링됩니다.
그렇기에, 상황에 따라서 최적화가 필요하기도 합니다.
이러한 이유들로 인해 Context API
는 작은 프로젝트에 적합합니다.
순서는 다음과 같습니다.
먼저, PokemonContext.jsx
를 생성해줍니다.
//PokemonContext.jsx import { createContext, useState } from "react"; import { toast } from "react-toastify"; // Context 생성 const PokemonContext = createContext(); // Provider 컴포넌트 생성 const PokemonProvider = ({ children }) => { const [selectedPokemon, setSelectedPokemon] = useState([]); const handelAddPokemon = (pokemon) => { if (selectedPokemon.some((p) => p.id === pokemon.id)) { toast.info("이미 추가된 포켓몬입니다."); return; } if (selectedPokemon.length > 5) { toast.info("최대 6개까지만 추가할 수 있습니다."); return; } setSelectedPokemon([...selectedPokemon, pokemon]); }; const handleRemovePokemon = (id) => { setSelectedPokemon(selectedPokemon.filter((p) => p.id !== id)); }; // Context Provider에서 관리하는 값 지정 return ( <PokemonContext.Provider value={{ selectedPokemon, handelAddPokemon, handleRemovePokemon }} > {children} </PokemonContext.Provider> ); }; export { PokemonContext, PokemonProvider };
const PokemonContext = createContext();
: 새로운 컨텍스트를 생성하고,
const PokemonProvider = ({ children }) => {
: 전역 상태와 함수를 공급 할 수 있는 컴포넌트를 생성합니다.
로직을 작성한 뒤, return
안에서 위와 같은 방식으로 value
를 전달해줍니다.
이러면 사용할 준비가 완료된것입니다.
다음은, App.jsx
에서 PokemonProvider
로 전체 앱 감싸줘야 합니다.
//App.jsx import AppRouter from "./shared/Router"; import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import { PokemonProvider } from "./shared/PokemonContext"; function App() { return ( <PokemonProvider> <AppRouter /> <ToastContainer /> </PokemonProvider> ); } export default App;
PokemonProvider
가 전체 앱을 감싸고 있으므로, 앱 내 어느 컴포넌트에서도 PokemonContext
를 통해 전역 상태에 접근할 수 있습니다.이제 상태관리가 필요한 컴포넌트에서 사용해보겠습니다.
//Dashboard.jsx import { useContext } from "react"; import { PokemonContext } from "../shared/PokemonContext"; import { DashboardImg, DashboardItem, DashboardList, DashboardMain, DashboardTitle, } from "../styles/DashboardStyles"; import PokemonCard from "./PokemonCard"; const Dashboard = () => { const { selectedPokemon } = useContext(PokemonContext); return ( <DashboardMain> <DashboardTitle>나만의 포켓몬</DashboardTitle> <DashboardList> {selectedPokemon.map((pokemon) => ( <PokemonCard key={pokemon.id} id={pokemon.id} korean_name={pokemon.korean_name} img_url={pokemon.img_url} onDashboard={true} /> ))} {Array.from({ length: 6 - selectedPokemon.length }).map((_, index) => ( <DashboardItem key={`empty-${index}`}> <DashboardImg src={"../public/pokeball-13iwdk7Y.png"} /> </DashboardItem> ))} </DashboardList> </DashboardMain> ); }; export default Dashboard;
useContext()
를 사용하여 필요한 value
값을 가져옵니다.Tip. useContext
는 Context API
와 함께 사용되는 전용 훅입니다.
Context API
로 생성한 Context
객체와만 사용 가능하며, 다른 용도로는 사용할 수 없습니다.
//PokemonCard.jsx import { useNavigate } from "react-router-dom"; import { PokemonCardButton, PokemonCardImg, PokemonCardInfoWrap, PokemonCardItem, PokemonCardName, PokemonCardNumber, } from "../styles/PokemonCardStyles"; import { useContext } from "react"; import { PokemonContext } from "../shared/PokemonContext"; const PokemonCard = ({ id, korean_name, img_url, onDashboard }) => { const nav = useNavigate(); const formattedId = id.toString().padStart(3, "0"); const { handelAddPokemon, handleRemovePokemon } = useContext(PokemonContext); const handleButton = (e) => { e.stopPropagation(); if (onDashboard) { handleRemovePokemon(id); } else { handelAddPokemon({ id, korean_name, img_url }); } }; return ( <PokemonCardItem onClick={() => { nav(`/pokemon-detail/${id}`); }} > <PokemonCardImg src={img_url} /> <PokemonCardInfoWrap> <PokemonCardName>{korean_name}</PokemonCardName> <PokemonCardNumber>No. {formattedId}</PokemonCardNumber> </PokemonCardInfoWrap> <PokemonCardButton onClick={handleButton}> {onDashboard ? "삭제" : "추가"} </PokemonCardButton> </PokemonCardItem> ); }; export default PokemonCard;
useContext
를 사용하여 필요한 함수들을 가져온 후 이하 로직을 완성해줍니다.이번 프로젝트를 하면서 다시 한 번 느끼게됐다.
나는 진짜로 할 수 있다.
혹시나 이것을 보고 있는 당신도 100% 해낼 수 있습니다.
함께 성장해갑시다 화이팅!