[๐Ÿ ํฌ์ผ“๋ชฌ ๋„๊ฐ ๋งŒ๋“ค๊ธฐ][๊ฐœ์ธ] Context API ์‚ฌ์šฉํ•ด์„œ ๋ฆฌํŒฉํ† ๋งํ•˜๊ธฐ

props drilling ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•œ ์ฝ”๋“œ๋ฅผ Context ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌํŒฉํ† ๋งํ•ด ๋ณด๋ ค ํ•œ๋‹ค. 

 

Context API ์‚ฌ์šฉ ์ „ ๊ตฌ์กฐ

์•„๋ž˜ ์ด๋ฏธ์ง€๋Š” ๋ฆฌํŒฉํ† ๋ง ์ „ props drilling ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•œ ์ปดํฌ๋„ŒํŠธ ๊ตฌ์กฐ์ด๋‹ค. 

 

Dashboard ์—์„œ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” removedPokemon ์„ PokemonCard ๋ฅผ ์œ„ํ•ด์„œ props ๋กœ ์ „๋‹ฌํ•ด ์ฃผ๊ณ  ์žˆ๋‹ค. ๋˜ํ•œ PokemonList ์—์„œ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” addPokemon ์„ props ๋กœ ์ „๋‹ฌํ•ด ์ฃผ๊ณ  ์žˆ๋‹ค. 

์ด ํ”„๋กœ์ ํŠธ๋Š” ๊ฐ„๋‹จํ•œ ๊ตฌ์กฐ๋กœ ๋˜์–ด์žˆ์–ด์„œ ์ถฉ๋ถ„ํžˆ props drilling ๋ฐฉ์‹์œผ๋กœ ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•ด ์ค˜๋„ ๊ดœ์ฐฎ๊ฒ ์ง€๋งŒ ๋งŒ์•ฝ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ช‡๋ฐฑ ๊ฐœ ๋˜๋ฉด props ๋ฅผ ์ „๋‹ฌ ์ „๋‹ฌ ์ „๋‹ฌ.... ํ•ด์•ผ๋  ๊ฒƒ์ด๋‹ค. 

์ด๋ฅผ ํ•ด์†Œํ•˜๊ธฐ ์œ„ํ•ด Context ๋ฅผ ์‚ฌ์šฉํ•ด ๋ณด์•˜๋‹ค. Context ๋Š” ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ props ๋ฅผ ์ „๋‹ฌํ•ด ์ฃผ์ง€ ์•Š์•„๋„ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ๋‹ค ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค! ๐Ÿ˜ฎ

 

Context API ์‚ฌ์šฉํ•ด์„œ ๋ฆฌํŒฉํ† ๋ง

1. context ์ƒ์„ฑ

import React, { createContext, useContext, useState } from 'react';

const PokemonContext = createContext();

export function usePokemonContext() {
  return useContext(PokemonContext);
}

export function PokemonProvider({ children }) {
  const [selectedPokemon, setSelectedPokemon] = useState([]);

  const addPokemon = (newPokemon) => {
    setSelectedPokemon((prev) => {
      return [...prev, newPokemon];
    });
  };

  const removePokemon = (pokemon) => {
    setSelectedPokemon((prev) => prev.filter((p) => p.id !== pokemon.id));
  };

  return (
    <PokemonContext.Provider value={{ selectedPokemon, addPokemon, removePokemon }}>
      {children}
    </PokemonContext.Provider>
  );
}

 

const PokemonContext = createContext();

createContext() ๋ฅผ ์‚ฌ์šฉํ•ด์„œ PokemonContext ๋ผ๋Š” ์ด๋ฆ„์˜ ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ–ˆ๋‹ค. ์ด ์ปจํ…์ŠคํŠธ๋Š” ํ”„๋กœ์ ํŠธ ์ „์ฒด์—์„œ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

 

export function usePokemonContext() { return useContext(PokemonContext); }

useContext ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปจํ…์ŠคํŠธ์— ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ์ปค์Šคํ…€ ํ›…์„ ๋งŒ๋“ค์—ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ์ปจํ…์ŠคํŠธ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์–ด๋””์„œ๋“  ์ด ํ›…์„ ํ˜ธ์ถœํ•˜์—ฌ ํฌ์ผ“๋ชฌ์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ ธ๋‹ค ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

export function PokemonProvider({ children }) {...}

PokemonProvider ์ปดํฌ๋„ŒํŠธ๋Š” ์ปจํ…์ŠคํŠธ ๊ณต๊ธ‰์ž ์—ญํ• ์„ ํ•œ๋‹ค. ์ด ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—๋Š” ํ•ด useState ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ์ •์˜ํ•œ ์ƒํƒœ์™€ addPokemon, removedPokemon ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•ด๋†“์•˜๋‹ค. 

 

return (
    <PokemonContext.Provider value={{ selectedPokemon, addPokemon, removePokemon }}>
      {children}
    </PokemonContext.Provider>
  );
}

PokemonContext.Provider ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋“ค์—๊ฒŒ value props ๋กœ ์ƒํƒœ์™€ ์ƒํƒœ๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•œ๋‹ค. ์ „๋‹ฌ๋œ ๊ฐ’๋“ค์€ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ ์–ด๋””์„œ๋“  ๊ฐ€์ ธ๋‹ค ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

2. ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์— ์ปจํ…์ŠคํŠธ ์‚ฌ์šฉ

import React from "react";
import Dashboard from "../components/Dashboard";
import PokemonList from "../components/PokemonList";
import MOCK_DATA from "../mock.js";
import { PokemonProvider } from "../context/PokemonContext.js";

const Dex = () => {
  return (
    <PokemonProvider>
        <Dashboard />
        <PokemonList pokemonList={MOCK_DATA} />
    </PokemonProvider>
  );
};


export default Dex;

์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์—ˆ์œผ๋ฉด ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์— PokemonProvider ๋กœ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ์ฃผ๋ฉด ํ•˜์œ„์˜ ๋ชจ๋“  ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ฐ’์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

3. ์‚ฌ์šฉ ์˜ˆ์‹œ

const PokemonCard = ({ pokemon, isSelected }) => {

  const { addPokemon, removePokemon } = usePokemonContext();

  ...

}

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌ์กฐ๋ถ„ํ•ดํ• ๋‹น์œผ๋กœ PokemonProvider ์ปดํฌ๋„ŒํŠธ์— ์ •์˜ํ•œ ํ•จ์ˆ˜๋“ค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์•„๋ž˜ ์ด๋ฏธ์ง€๋Š” ๋ฆฌํŒฉํ† ๋ง ํ›„ ๊ตฌ์กฐ๋ฅผ ํ‘œํ˜„ํ•ด๋ณธ ๊ฒƒ์ด๋‹ค.

 

ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ props ๋กœ ์ž์‹์—๊ฒŒ ์ „๋‹ฌํ•ด ์ฃผ์ง€ ์•Š์•„๋„ ์ž์‹์€ context ์—์„œ ๋ฐ”๋กœ ๊ฐ€์ ธ๋‹ค๊ฐ€ ์“ธ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ํ‘œํ˜„ํ•ด ๋ณด์•˜๋‹ค. ๐Ÿ˜…

 

PokemonDetail ์—๋„ Context ์‚ฌ์šฉํ•˜๊ธฐ

PokemonDetail ์— '์ถ”๊ฐ€' ๋ฒ„ํŠผ์„ ๋„ฃ์–ด์„œ ํ•ด๋‹น ํŽ˜์ด์ง€์—์„œ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ ์„ ํƒ๋œ ํฌ์ผ“๋ชฌ์œผ๋กœ ํ•˜๊ธฐ ์œ„ํ•œ ์ž‘์—…์„ ์ถ”๊ฐ€๋กœ ์ง„ํ–‰ํ–ˆ๋‹ค.

 

์•„๋ž˜๋Š” Detail ํŽ˜์ด์ง€์— context ๋ฅผ ์ ์šฉํ•ด ๋ณธ ๊ฒƒ์ด๋‹ค. (์˜ค๋ฅ˜ ๋‚จ..)

import React from "react";
import styled from "styled-components";
import { useNavigate, useParams } from "react-router-dom";
import { DEX_URL } from "../utils/path.js";
import MOCK_DATA from "../mock.js";
import { usePokemonContext } from "../context/PokemonContext.jsx";
import { PokemonProvider } from "../context/PokemonContext.jsx";

const PokemonDetail = () => {
  const navigate = useNavigate();
  const pokemonId = useParams().id;

  const { addPokemon } = usePokemonContext();

  const pokemon = MOCK_DATA.find((p) => {
    return p.id === Number(pokemonId);
  });

  return (
    <PokemonProvider>
      <StyledSection>
        <img src={pokemon.img_url} />
        <span>{pokemon.korean_name}</span>
        <span>{pokemon.types.join(", ")}</span>
        <p>{pokemon.description}</p>
        <button
          onClick={() => {
            console.log("์ƒ์„ธํŽ˜์ด์ง€์—์„œ ํฌ์ผ“๋ชฌ ์ถ”๊ฐ€");
          }}
        >
          ์ถ”๊ฐ€ํ•˜๊ธฐ
        </button>
        <button
          onClick={() => {
            navigate(DEX_URL);
          }}
        >
          ๋’ค๋กœ๊ฐ€๊ธฐ
        </button>
      </StyledSection>
    </PokemonProvider>
  );
};

export default PokemonDetail;

 

Dex ์—์„œ ํ–ˆ๋˜ ๊ฒƒ ์ฒ˜๋Ÿผ PokemonProvider ๋กœ ๊ฐ์‹ธ๊ณ  usePokemonContext() ๋กœ ํ•„์š”ํ•œ ๊ฒƒ ๊บผ๋‚ด์„œ ์“ฐ๋ฉด ๋˜๊ฒ ๋‹ค! ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐ.. ์˜ค๋ฅ˜๊ฐ€ ๋ปฅ๋ปฅ ๋‚ฌ๋‹ค. ๊ทธ ์ด์œ ๋Š”.. ์ฐธ ์–ด์ด์—†๋Š” ์ด์œ ์ธ๋ฐ..

๋””ํ…Œ์ผ ํŽ˜์ด์ง€๋Š” ํ•œ ๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค. ๋‚˜๋Š” context ๋ฅผ ๊ทธ PokemonDetail ์ปดํฌ๋„ŒํŠธ์— return ์ชฝ์— ๊ฐ์ŒŒ๊ณ , PokemonDetail ์ปดํฌ๋„ŒํŠธ์—์„œ usePokemonContext() ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. ๐Ÿ˜‚ ์ฆ‰, PokemonContext.Provider ๋กœ ๊ฐ์‹ธ์ง„ ์ปดํฌ๋„ŒํŠธ ๊ณ„์ธต ๊ตฌ์กฐ ๋‚ด์—์„œ ํ˜ธ์ถœํ•˜์ง€ ์•Š์€ ๊ฒƒ!

 

์ฒซ ๋ฒˆ์งธ ์ƒ๊ฐํ•œ ํ•ด๊ฒฐ์ฑ…์œผ๋กœ PokemonDetail ์— ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด์„œ ๊ทธ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ context ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ•˜๋ฉด ๋˜๊ฒ ๋‹ค! ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐ, ๊ณต๋ถ€ํ•ด ๋ณธ ๊ฒฐ๊ณผ ๋งŒ์•ฝ ์ด ๋ฐฉ๋ฒ•๋Œ€๋กœ PokemonContext ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด .. Dex ํŽ˜์ด์ง€์™€ PokemonDetail ํŽ˜์ด์ง€๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ์ƒํƒœ๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค. ์™œ๋ƒ๋ฉด.. PokemonProvider ๊ฐ€ ๋ณ„๋„์˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ! 

 

๋‘ ๋ฒˆ์งธ๋กœ ์ƒ๊ฐํ•œ ํ•ด๊ฒฐ์ฑ…์€ Dex ์™€ PokemonDetail ๋ฅผ ๊ฐ™์ด Provider ๋กœ ๊ฐ์‹ธ๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๋‘ ๊ฐœ๋ฅผ ๊ณตํ†ต์œผ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐพ์•„์„œ PokemonProvider ๋กœ ๊ฐ์‹ธ๋ฉด ๋œ๋‹ค!

 

App ์ปดํฌ๋„ŒํŠธ๋ฅผ PokemonProvider ๋กœ ๊ฐ์‹ธ์ฃผ๋ฉด ๋ ๐Ÿ”ฅ

import "./App.css";
import { PokemonProvider } from "./context/PokemonContext";
import Router from "./shared/Router";

function App() {
  return (
    <PokemonProvider>
      <Router />
    </PokemonProvider>
  );
}

export default App;

 

๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด ๊ตฌ์กฐ

 

๋ฌธ์ œ ํ•ด๊ฒฐ ํ›„ ๊ตฌ์กฐ

 

 

์ด์ œ PokemonDetail ์—์„œ PokemonContext ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๊ณ  Dex ์™€ PolemonDetail ๋‘ ๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋™์ผํ•œ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ, Dex ์—์„œ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด PokemonDetail ์—๋„ ์ƒํƒœ๊ฐ€ ๋ฐ˜์˜๋˜๊ณ  ๊ทธ ๋ฐ˜๋Œ€๋„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค!!