• 티스토리 홈
  • 프로필사진
    redpome
  • 방명록
  • 공지사항
  • 태그
  • 블로그 관리
  • 글 작성
redpome
  • 프로필사진
    redpome
    • 분류 전체보기 (50)
      • 내일배움캠프 (23)
      • 웹개발 지식 (2)
      • 프로그래머스 (8)
      • React (7)
      • 코딩테스트 (1)
      • UI-UX (1)
      • 타입스크립트 (2)
      • Next.js (3)
  • 방문자 수
    • 전체:
    • 오늘:
    • 어제:
  • 최근 댓글
      등록된 댓글이 없습니다.
    • 최근 공지
        등록된 공지가 없습니다.
      # Home
      # 공지사항
      #
      # 태그
      # 검색결과
      # 방명록
      • 포켓몬 도감 만들기 -(1)
        2025년 02월 05일
        • redpome
        • 작성자
        • 2025.02.05.:02

        이번 과제는 포켓몬 도감 만들기다. Props drilling 방식부터 시작하여 Context를 통한 상태관리부터 Redux ToolKit 까지의 리팩토링을 목표로하고있다. CSS 다루기도 그렇고 리팩토링에 부족한 부분까지 채울려면 할 게 너무 많다.

        오늘 개인 면담에서도 코딩 경험이 많이 부족하다는 말을 듣다보니 심각한것 같다. 실시간으로 수업을 듣다보면 말로 설명하는 것이 머릿 속에 들어오지 않는걸 보면 태생적인것도 있지만 아직 많이 부족한 학습시간도 한 몫 한 것 같다. 2월달에도 왜 이렇게 밖에 나갈 일이 많은건지...

         


         

        원래대로라면 Context를 통한 상태관리까지 하는 것이 맞지만 일단  여기까지 한 것을 기록으로 남겨야겠다. 

        일단 포켓몬 도감을 위해 만든 프로젝트의 구조는 다음과 같다.

         

        src

        ├─assets
        ├─components
        ├─pages
        ├─GlobalStyle.jsx => 전역 스타일을 관리하기 위해 만든 파일
        └─shared


        주로 쓰는 구조를 위주로 보이도록하겠다. assets에는 MOCK_DATA라고 해서 포켓몬의 정보를 담고 있는 파일이 있다. 
        │   ├── components/
        │   │   ├── Dashboard.jsx => 포켓몬 선택 시 추가할 컴포넌트
        │   │   ├── PokemonCard.jsx => 포켓몬 정보가 담긴 개별 카드의 구조를 다루는 컴포넌트
        │   │   ├── PokemonDetail.jsx => 쿼리스트링을 이용하여 카드 클릭 시 세부 정보를 보여주는 컴포넌트
        │   │   ├── PokemonList.jsx =>포켓몬 카드를 렌더링할 카드리스트 컴포넌트
        │   ├── pages/
        │   │   ├── Home.jsx => 최초 페이지 접속 시의 화면
        │   │   ├── Detail.jsx => 세부사항을 보여주는 컴포넌트가 렌더링되는 페이지
        │   │   ├── Dex.jsx => 대시보드, 카드 컴포넌트들을 보여주는 페이지
        │   ├── App.js
        │   ├── shared/
        │   │   ├── Router.jsx => 라우터 설정을 위한 파일

        대략적으로 중요한 파일들의 구조는 위와 같다.


        컴포넌트간 동작의 흐름

        일단 프로젝트는 3개의 branch로 나뉘는데 prop-drilling->context->redux 이렇게 상태관리에 따른 차이를 구별하기 위해 브랜치를 생성하고 프로젝트를 만들었다.
        처음은 상위 컴포넌트에서 하위 컴포넌트로 Props를 전달하는 방식의 Prop-drilling 방식을 통해 구현하였다.

        지난 리액트 과제에서는 리스트 컴포넌트와 리스트 컴포넌트에서 렌더링 할 컴포넌트을 명확히 분리시키는데에 실패했었다.

         

        const PokemonCard = ({ pokemonData, addPokemonCard }) => {
          const navigate = useNavigate();
          const showPokemonDetail = () => {
            navigate(`/detail?id=${pokemonData.id}`);
          };
          return (
            <div>
              <PokemonCardFrame onClick={() => showPokemonDetail(pokemonData.id)}>
                <div>
                  <img src={pokemonData.img_url}></img>
                </div>
                <div>{pokemonData.korean_name}</div>
                <div>{pokemonData.types}</div>
                <div>No. {pokemonData.id}</div>
                <button onClick={() => addPokemonCard(pokemonData.id)}>추가</button>
              </PokemonCardFrame>
            </div>
          );
        };
        export default PokemonCard;

         

        포켓몬 카드를 만드는 컴포넌트에서는 상위 컴포넌트로부터 데이터를 내려받고 개별적인 카드의 구조를 만들기위한 컴포넌트로서 사용한다.

         

        const PokemonList = ({ addPokemonCard, pokemonData }) => {
          return (
            <PokemonListContainer>
              {" "}
              {pokemonData.map((pokemon) => (
                <PokemonCard
                  key={pokemon.id}
                  pokemonData={pokemon}
                  addPokemonCard={addPokemonCard}
                />
              ))}
            </PokemonListContainer>
          );
        };
        export default PokemonList;

         

        리스트 컴포넌트는 하위 컴포넌트인 카드 컴포넌트에 props를 넘겨주고 하위 컴포넌트를 감싸 렌더링할 수 있도록한다.

        const Dex = () => {
          const [pokemonInBall, setPokemonInBall] = useState([]);
          console.log(pokemonInBall);
        
          const addPokemonCard = (id) => {
            const selectedCard = POKEMON_DATA.find((card) => card.id === id);
            // console.log(selectedCard);
        
            if (selectedCard) {
              if (pokemonInBall.length >= 6) {
                alert("최대 6마리까지 데리고 다닐 수 있습니다");
                return;
              }
              setPokemonInBall((selectedPokemon) => [...selectedPokemon, selectedCard]);
            }
          };
          const deletePokemonCard = (id) => {
            setPokemonInBall((prevPokemon) =>
              prevPokemon.filter((card) => card.id !== id)
            );
          };
          return (
            <Container>
              <DashBoard
                pokemonInBall={pokemonInBall}
                deletePokemonCard={deletePokemonCard}
              />
              <PokemonList
                pokemonData={POKEMON_DATA}
                addPokemonCard={addPokemonCard}
                deletePokemonCard={deletePokemonCard}
              ></PokemonList>
              
            </Container>
          );
        };

         

        prop-drilling 방식을 위해 일단 dex 페이지에서의 부모 컴포넌트인 Dex 컴포넌트에서 함수나 데이터를 넘겨주도록하였다.

         

        Dex->PokemonList->PokemonCard 컴포넌트로 props가 전달이 될 것이다.
        대시보드에 카드를 추가하기 위해 버튼 이벤트를 위한 이벤트 흐름을 정리하면 다음과 같다.

        1. PokemonCard 컴포넌트에서 생성한 버튼을 클릭, 이벤트 함수는 Dex에서 선언하고 props를 통해 전달하였다.
        2. PokemonCard의 상위 컴포넌트인 PokemonList 컴포넌트는 Dex로부터 전달받은 추가 함수를 PokemonCard에게 전달
        3. Dex 컴포넌트에서는 대시보드에 추가할 포켓몬들의 상태를 위해 useState를 선언하고 추가 함수는 해당 상태를 통해 관리한다.
        4. Dex 컴포넌트는 Dashboard 컴포넌트에 PokemonInBall이라는 상태를 넘겨준다.
        5. Dashboard 컴포넌트는 Dex 컴포넌트로부터 받은 상태를 렌더링한다.
          return (
            <Container>
              <DashBoard
                pokemonInBall={pokemonInBall}
                deletePokemonCard={deletePokemonCard}
              />
              <PokemonList
                pokemonData={POKEMON_DATA}
                addPokemonCard={addPokemonCard}
                deletePokemonCard={deletePokemonCard}
              ></PokemonList>
              
            </Container>

         

        Dex 페이지에서는 위와 같은 컴포넌트들의 배치와 props를 넘겨주는 것을 확인할 수 있다. 

         

        문제가 됐던 점

         

        예시로 들었던 프로젝트를 보면 카드를 선택하지 않은 상태에서는 빈 포켓몬 볼이 대시보드에 존재한다. 그리고 추가하면 동적으로 빈 포켓몬 볼 대신 포켓몬 카드가 입력이 되었다.

         

        이런 식으로 말이다.

        여기서 난 아예 이런 기능을 구현할 수가 없었다. 처음에는 PokeBall 이라는 컴포넌트를 생성하고 삼항 연산자를 통해 Dashboard로 넘겨준 PokemonInBall의 상태에 따라 컴포넌트를 렌더링 할려고했으나 동적인 렌더링을 주는 것에 실패했다.

         

        결국 이 부분은 튜터님과 AI를 통해 해결하고 내가 쓴 코드는 없었다.

         

              <SelectedPokemonBoard>
                <h1>좋아하는 포켓몬은?</h1>
                <PokeBallContainer>
                  {Array.from({ length: maxPokemon }, (_, index) => {
                    if (index < pokemonInBall.length) {
                      const bag = pokemonInBall[index];
                      return (
                        <PokemonCard key={bag.id}>
                          <img
                            src={bag.img_url}
                            alt={bag.korean_name}
                            style={{ width: "100%" }}
                          />
                          <div>{bag.korean_name}</div>
                          <div>{bag.types.join(", ")}</div>
                          <div>No. {bag.id}</div>
                          <button onClick={() => deletePokemonCard(bag.id)}>
                            삭제
                          </button>
                        </PokemonCard>
                      );
                    } else {
                      return (
                        <PokeBall key={`empty-${index}`}>포켓몬을 고르세요</PokeBall>
                      );
                    }
                  })}
                </PokeBallContainer>
              </SelectedPokemonBoard>

         

        Array객체 생성을하고 from 메서드를 사용했다. Array.from은 유사 배열 객체를 만들때 주로 사용한다고한다.
        그러니까 현재 내 코드에서는 PokemonInBall에 있는 데이터를 가지고 대시보드에 카드를 렌더링해야하니 maxPokemon=6만큼의 가상의 배열을 만든 것이다.

        (_,index)는 mapFn에 해당하는 함수이며 배열의 모든 요소에 대해 호출한다. _은 값을 사용하지 않는다는 의미이고 원래 해당 자리는 배열에서 현재 처리 중인 요소이다, 나는 현재 요소의 인덱스가 필요로한데 PokemonInBall에는 카드가 배열에 담기기 때문이다.

         

        만약 카드를 1개 추가했다고하자 PokemonInBall.length=1이고 index=0이다. 그렇다면 if문에 선언된 코드가 실행된다. index=1에서는 다시 else문이 실행된다. 이를 통해 동적으로 카드를 렌더링 할 수 있는 것이다.

         

        '내일배움캠프' 카테고리의 다른 글

        포켓몬 도감 트러블 슈팅 - 카드 중복 에러  (0) 2025.02.11
        TIL - UseContext 사용기, 트러블 슈팅  (1) 2025.02.06
        TIL - 트러블 슈팅  (0) 2025.01.23
        실행 컨텍스트 (3) - 콜 스택, 마이크로 태스크  (0) 2025.01.17
        TIL - Scope, this 바인딩, 클래스  (2) 2025.01.15
        다음글
        다음 글이 없습니다.
        이전글
        이전 글이 없습니다.
        댓글
      조회된 결과가 없습니다.
      스킨 업데이트 안내
      현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
      ("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)
      목차
      표시할 목차가 없습니다.
        • 안녕하세요
        • 감사해요
        • 잘있어요

        티스토리툴바