개인 프로젝트
이번에 만들 건 포켓덱스(포켓몬 도감.)
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로부터 각각 포켓몬의 아이디만 담긴 배열, 포켓볼이 차있는지, 비어있는지에 대한 여부가 담긴 배열을 각각 만들어 처리하였다.