본 캠프

리액트 숙련. (3)

Iruka913 2024. 11. 7. 21:00

개인 프로젝트

이번에 만들 건 포켓덱스(포켓몬 도감.)

 

1세대 포켓몬 151마리를 전부 기록해놓은 포켓몬 도감에 더불어, 포켓몬을 클릭하면 그 포켓몬에 해당되는 데이터가 다른 페이지에 별도로 표시되고, 엔트리에 포켓몬을 집어넣을 수 있는 기능을 구현하면 된다고 한다. 

 

초안. 

 

홈 화면.

 

도감 화면

 

폰트를 적용했고, 클릭할 시 포켓몬 특유의 띵! 효과음이 나게끔 했다. 

라우터 설정. 

import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../pages/Home";
import Dex from "../pages/Dex";
import PokemonDetail from "../pages/PokemonDetail";

const Router = ({ pokeLists, setPokeLists }) => {
    return (
        <BrowserRouter>
            <Routes>
                <Route path="/" element={<Home />} />
                <Route
                    path="/dex"
                    element={
                        <Dex
                            pokeLists={pokeLists}
                            setPokeLists={setPokeLists}
                        />
                    }
                />
                <Route path="/pokemon-detail/:id" element={<PokemonDetail />} />
            </Routes>
        </BrowserRouter>
    );
};
export default Router;

 

라우터는 크게 세 종류. 홈, 도감 화면, 그리고 상세 화면이다. 상세 화면의 경우에는 dynamic route를 통해 구현할 거리가 좀 있어서, 끝에 :id를 붙여놓았다. 

 

홈 화면

import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import { CenterWrapper } from "../commonStyles/commonStyles";
import useSound from "use-sound";
import pokebeep from "../assets/pokebeep.mp3";

const StHomeWrapper = styled.div`
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100dvh;
`;

const StBtn = styled.button`
    width: 170px;
    height: 50px;
    margin-top: 10px;
    background-color: red;
    color: white;
    border: solid 1px;
    border-radius: 8px;
    font-size: 20px;
    transition: 0.5s;
    &:hover {
        background-color: #f5a9a9;
        transition: 0.5s;
        cursor: pointer;
    }
    font-family: "Pokemon";
`;

const BeepButton = ({ onClick, children }) => {
    const [play] = useSound(pokebeep);
    return (
        <StBtn
            onClick={() => {
                play();
                onClick();
            }}
        >
            {children}
        </StBtn>
    );
};

const Home = () => {
    const nav = useNavigate();
    return (
        <StHomeWrapper>
            <CenterWrapper>
                <img
                    src="src\assets\Pokédex_logo.png"
                    alt="포켓 덱스 로고"
                    height={"250px"}
                />
                <BeepButton
                    onClick={() => {
                        nav("/dex");
                    }}
                >
                    포켓 덱스 시작
                </BeepButton>
            </CenterWrapper>
        </StHomeWrapper>
    );
};
export default Home;

 

스타일드 컴포넌트와 그냥 함수 컴포넌트를 같이 적용하는 게 문제였는데, 칠드런 요소를 받는 것으로 적용할 수 있었다. 아직 칠드런 요소의 적용이 미숙했지만, 슬슬 어떻게 쓰는 지 감이 올 것만 같다. 

도감에서 대쉬 보드로

const addToEntry = (id, pokeLists, setPokeLists) => {
    let updated = false;

    const newPokeLists = pokeLists.map((pokeList) => {
        if (!updated && !pokeList.filled) {
            updated = true;
            return { idx: pokeList.idx, pokeid: id, filled: true };
        }
        return pokeList;
    });
    setPokeLists(newPokeLists);
};

const PokemonCard = ({ pokeLists, setPokeLists }) => {
    const nav = useNavigate();
    return MOCK_DATA.map((data) => {
        return (
            <BeepPokeCard
                key={data.id}
                onClick={() => {
                    nav(`/pokemon-detail/${data.id}`);
                }}
            >
                <img src={data.img_url} alt="포켓몬 이미지입니다." />
                <span>{data.korean_name}</span>
                <SmallBeepButton
                    onClick={() => {
                        addToEntry(data.id, pokeLists, setPokeLists);
                    }}
                >
                    추가
                </SmallBeepButton>
            </BeepPokeCard>
        );
    });
};

 

addToEntry 버튼을 누를 시, pokeLists를 수정하게끔 로직을 짰다. 

 

버튼을 누르면 모든 pokeList의 filled 값이 true로 바뀌는 사소한 찐빠가 있었지만 updated라는 별개의 변수를 둠으로서, 한 번 실행되는 updated가 true로 변경되어 첫 번째 filled 값이 true로 변경되는 순간, map으로 인해 다른 값들도 한꺼번에 바뀌지 않게끔 바뀌었다. 

대쉬보드에서 삭제. 

누가 배틀을 이렇게 한담?

    const deleteFromEntry = (idx) => {
        const newPokeLists = pokeLists.map((pokeList) => {
            if (pokeList.idx === idx) {
                return { ...pokeList, pokeid: "", filled: false };
            }
            return pokeList;
        });
        setPokeLists(newPokeLists);

 

상속받은 pokeLists와 setPokeLists를 바탕으로 간단한 삭제 로직을 구현하였다.

 

올림픽 메달 트래커와는 달리, 빈 몬스터볼 이미지를 표현하기 위해서는 실질적으로는 배열의 삭제가 아니라 수정이 필요해서, 이런 식으로 구현해주었다. 

 

각각 pokeid는 ""으로, filled는 false로 바꿔서 데이터를 초깃값으로 되돌려주었다. 

validation

이봐, 포켓몬은 6마리까지 들고 다닐 수 있다고.

 

리자몽을 두 개 넣고 싶다고? 안 돼.

 

const addToEntry = (id, pokeLists, setPokeLists) => {
    const valueofPokeIdArray = [];
    const valueofFilledArray = [];
    for (const pokeList of pokeLists) {
        let valueofPokeId = Object.values(pokeList)[1];
        let valueofFilled = Object.values(pokeList)[2];
        valueofPokeIdArray.push(valueofPokeId);
        valueofFilledArray.push(valueofFilled);
    }
    if (!valueofFilledArray.includes(false)) {
        alert("배틀 엔트리가 꽉 찼습니다.");
        return;
    }
    if (valueofPokeIdArray.includes(id)) {
        alert("중복된 포켓몬을 선택하였습니다. 다른 포켓몬을 선택해주세요.");
        return;
    }
    let updated = false;
    const newPokeLists = pokeLists.map((pokeList) => {
        if (!updated && !pokeList.filled) {
            updated = true;
            return { idx: pokeList.idx, pokeid: id, filled: true };
        }
        return pokeList;
    });
    setPokeLists(newPokeLists);
};

 

중복된 포켓몬을 넣거나. 포켓몬을 6마리 이상 선택하였을 때, validation을 넣어서 안 된다고 alert를 출력하게끔 만들었다.

 

Object.values()를 이용해 pokeList로부터 각각 포켓몬의 아이디만 담긴 배열, 포켓볼이 차있는지, 비어있는지에 대한 여부가 담긴 배열을 각각 만들어 처리하였다. 

'본 캠프' 카테고리의 다른 글

리액트 숙련. (5)  (0) 2024.11.11
리액트 숙련. (4)  (1) 2024.11.08
리액트 숙련. (2)  (0) 2024.11.05
리액트 숙련. (1)  (0) 2024.11.04
리액트 입문 1주차. (5)  (1) 2024.11.01