[개인과제] 포켓몬 도감 만들기2

안현희·2024년 11월 8일
0

React를 배워보자!

목록 보기
15/20
post-thumbnail

이어서 과제를 해보자

  • 자아~ 시작!

1. Context API 리팩터링

🤔 근데 왜 Context API를 사용하려는걸까요?

React에서 데이터를 전달하는 기본 방식은 Props입니다.
부모 컴포넌트자식 컴포넌트데이터를 전달하는 방식이죠.
그러나, Props방식은 여러 단계의 컴포넌트를 거쳐 데이터를 전달해야 할 때 상당히 비효율적이 됩니다.
부모-자식 관계의 깊이가 깊어질 때 발생하는 현상을 우리는 Prop Drilling이라 부르는데 바로 이 문제를 해결하려고 사용하는것입니다.

아래의 이미지를 함께 살펴보겠습니다.

  • 만약 이런 구조의 프로젝트가 있다고 했을때,

  • 혹시라도 이렇게 어플리케이션이 커진다면?
    위와 같이 Prop Drilling이 일어날것입니다.

  • 이럴때 사용할 수 있는것이 바로 Context API입니다.
  • Context API를 통해 우리는 Prop Drilling를 해결 할 수 있고, 데이터를 전역적으로 관리를 할 수 있습니다.

⭐ 알아두면 좋아요.

  • Context API 상태를 직접 관리하지 않고 useState나 useReducer를 통해 이루어집니다.
    다시 말해, 데이터를 전달하는 역할만 한다는 뜻입니다.

  • Context API는 데이터가 변경되면 모든 자식 컴포넌트가 리렌더링됩니다.
    그렇기에, 상황에 따라서 최적화가 필요하기도 합니다.

  • 이러한 이유들로 인해 Context API는 작은 프로젝트에 적합합니다.


😏 이제 본격적으로 리팩토링을 해봅시다.

순서는 다음과 같습니다.

  1. Context 파일 생성 및 Provider 설정
  2. Provider로 앱을 감싸기
  3. 필요한 컴포넌트에서 Context 사용하기

먼저, 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. useContextContext 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를 사용하여 필요한 함수들을 가져온 후 이하 로직을 완성해줍니다.

끝났습니다. 생각보다 간단하죠?


2. Vercel에 프로젝트 배포하기

나만의 포켓몬 도감
소스코드


회고

  • 이번 프로젝트를 하면서 다시 한 번 느끼게됐다.
    나는 진짜로 할 수 있다.

  • 혹시나 이것을 보고 있는 당신도 100% 해낼 수 있습니다.
    함께 성장해갑시다 화이팅!

그럼이만

0개의 댓글