[24.08.22-23] 개인프로젝트 1,2일차 - 포켓몬 도감 만들기

숙련주차 강의 끝내고 드디어 프로젝트 시작~~

원래는.. 화요일에 시작하고 싶었는데 챌린지반 과제 중간중간 들어오고 리덕스 실습에서 에러로 막히고 ㅋㅋㅋ ㅠㅠ 하느라 생각보다 늦어졌다..

 

 

이번 목표

이번에 해보고싶은 거!

1) 지난번 개인과제에서 폴더 구조를 고민해보라는 피드백을 받았다. 컴포넌트 폴더 기능/맥락으로 나눠보고 피드백받기!

2) 기능 구현 먼저 빨리하고 스타일 -> 리팩토링 적용하기

 

아자~~

 

 

Button, NavButton 컴포넌트

지난번에 Button, Input 같은 태그를 한 겹 감싸는 태그를 만들어보고싶다 했었는데 첫 랜딩페이지부터 버튼이 필요해서 한 번 만들어봤다.

이번엔 react-router-dom을 이용하는 것이 미션이어서 페이지 이동이 있는데 과제에서는 페이지 이동이 많이 없지만.. NavButton을 만들어두면 다른 이동이 필요할 때 편하게 이용할 수 있을 것 같아 만들었다

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

const PAGE_PATH = {
  Home: "/",
  Dex: "/dex",
};

export const getPath = (page) => PAGE_PATH[page];

const Router = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path={PAGE_PATH.Home} element={<Home />} />
        <Route path={PAGE_PATH.Dex} element={<Dex />} />
      </Routes>
    </BrowserRouter>
  );
};

export default Router;
// Button.jsx
const Button = ({ onClick, children, ...props }) => {
  return (
    <button onClick={onClick} {...props}>
      {children}
    </button>
  );
};

export default Button;
// NavButton.jsx
import { useNavigate } from "react-router-dom";
import { getPath } from "../../shared/Router.jsx";
import Button from "./Button.jsx";

const NavButton = ({ page, children }) => {
  const navigate = useNavigate();
  const navHandler = (page) => {
    const path = getPath(page);
    navigate(path);
  };

  return (
    <Button
      onClick={() => {
        navHandler(page);
      }}
    >
      {children}
    </Button>
  );
};

export default NavButton;

 

 

정해진 개수만큼 슬롯 만들기

선택한 포켓몬이 저 슬롯들에 들어가고, 선택할 수 있는 포켓몬의 수는 정해져있다. (6)

지금까지는 어떤 정보가 담긴 배열이 있고, 그 배열을 .map 등을 이용해서 컴포넌트 배열을 반환했는데 정해진 수로 반복을 하려니까 생각나는 방법이 없었다.

 

반환돼야하는 값이 어쨌든 컴포넌트의 배열이니까 우선 정해진 수로 배열을 생성할 수 있는 방법을 찾아봤는데 Array.from이라는 메서드가 있었다!

 

<div style={{ display: "flex" }}>
  {Array.from({ length: SELECTABLE_POKEMON_NUM }, (_, i) => (
    <Slot key={i} />
  ))}
</div>

 

styled-component로 컴포넌트에 스타일 적용하기

https://nninyeong.tistory.com/92

 

[React] 컴포넌트에 styled-components 로 스타일 적용하기

개인프로젝트로 열심히 기능 구현을 하고 styled-component를 처음 활용해보려는데.. 시작부터 난관에 막혔다 🥹일반적인 div 태그 등에는 잘 적용이되는데 내가 만든 컴포넌트에는 아무리 시도해도

nninyeong.tistory.com

제대로 이해를 해둬야할 것 같아서 따로 정리했다.

styled-component가 컴포넌트에 전혀 적용이 되지 않고 에러도 일으키지 않아서 한참 헤맸는데, 정리해보자면 아래와 같다

 

1) 컴포넌트는 실제 브라우저에 렌더링되는 요소가 아니고, Virtual DOM을 구성하기 위한 함수

2) styled-components는 기존 컴포넌트를 렌더링하면서 스타일 속성을 props로 전달한다. 기본 HTML 태그에는 특별히 설정하지 않아도 자동으로 적용되지만, 따로 만든 컴포넌트에는 이 props를 어떻게 활용할 것인지 명시해야 함

3) 실제 렌더링되는 요소에 스타일을 적용하기 위해선 실제 DOM 요소 (HTML 태그)로 props를 전달해줘야 함

 

1일차 마무리

이번 목표가 빠르게 기능 구현 후 나머지 요구사항들 덧붙이기 였는데.. 필수 기능이 저녁시간까지 완성이돼서 너무 뿌듯했다

그래서 자기 전에 대략적인 스타일을 다 적용하려고 했는데 오개념이 있어서 예상치못하게 해결에 시간이 오래걸렸다 ㅠㅠ

내일 스타일 적용이랑 context api로 리팩토링하기는 꼭 마무리할 수 있길!

 

 

grid로 슬롯, 카드 정렬하기

슬롯

화면이 커졌을 때 슬롯이 커졌을 때 너무 못생겨서.. max-width를 제한했더니 6개로 나눠진 grid 내부에서 왼쪽 정렬이 되면서 margin-right를 잘못설정해둔듯한 모습이됐다

 

1) 시도한 것

처음엔 진짜 margin인 공간이라고 생각하고  margin으로 가운데정렬하는 방법을 시도했다

margin: 0 auto;

 

알아보니 grid 속성을 조정할 수 있는 방법 중 justify-items를 사용하니 의도대로 적용이 됐다!

존재는 알고있었지만 flex를 사용할 때는 사실 사용할 일이 없어서 써본적이 없었는데 다행히 쉽게 적용이됐다

 

2) 완성

const StyledSlotContainer = styled.div`
  width: 100%;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  justify-items: center;
  align-items: center;
  column-gap: 10px;
`;

const StyledSlot = styled.div`
  border: 1px dashed var(--grey);
  padding: 20px;
  background: white;
  width: 100%;
  max-width: 100px;
  height: fit-content;
`;

 

계속 시간에 쫓겨 좀 더 익숙한 flex를 쓰다보니 grid가 익숙해지지 않아서 이번엔 grid를 여러 곳에 활용해보고 싶었는데 작은 공간을 만드는데도 참 난관이 많았다 ㅋㅋㅋ 기억해두면 좋을 것 같아서 나머지도 기록..

- 선택된 포켓몬이 있어서 카드와 빈 슬롯이 같이 정렬됐을 때는 두 컴포넌트의 높이가 달라 위와같이 위와 같이 정렬이돼 align-items: center를 이용해 가운데 정렬을 맞춰줬다

 

* justify-content: 컨테이너 내부 전체 요소들의 주축 기준 정렬

* justify-items: 컨테이너 내부 개별 요소들의 주축 기준 정렬

 

포켓몬 카드 리스트

지난번 영화 카드에서는 설정을 실패해서 적당히 타협했던.. 카드 배치!!! 드디어 성공했다

카드 전체가 컨테이너에서 가운데 정렬이 되면서, 제일 마지막 줄에 남아있는 요소는 제일 앞으로 가는 형태로 배열하고 싶어서 많이 헤맸었는데 드디어 방법을 알았다

 

이렇게 오른쪽에 애매한 틈이 남는게 싫었다..

근데 지금 생각해보니까 슬롯이랑 같은 문제네 ㅋㅋㅋㅋ

 

완성! display: grid를 설정해둔 container 내부의 요소들에 width를 설정하지 말고 grid가 크기를 잘 맞추도록 놔두거나 container에서 justify-items:center를 설정해주면 된다

 

const StyledList = styled.div`
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  justify-items: center;
  align-items: center;
  gap: 20px;

  width: 100%;
  padding: 20px;
  border-radius: 10px;

  background-color: var(--light-grey);
`;

 

 

context API로 리팩터링하기

현재 여러 컴포넌트에서 사용되고 있고, props drilling이 여러 차례 일어나는 state는 선택된 포켓몬의 정보를 담고있는 selectedPokemon이었다!

 

아래 내용을 참고해서 만들었다

https://velog.io/@velopert/react-context-tutorial

 

1) context 만들기

import { createContext } from "react";
export const PokemonContext = createContext(null);

 

2) Provider 만들기

import { useState } from "react";
import { PokemonContext } from "./PokemonContext";

const PokemonContextProvider = ({ children }) => {
  const selectedPokemonState = useState([]);

  return <PokemonContext.Provider value={selectedPokemonState}>{children}</PokemonContext.Provider>;
};

export default PokemonContextProvider;

 

3) 자식 컴포넌트로 전달하던 props들을 없애주고, 자식컴포넌트에서 useContext로 값을 받아오거나 custom hook 만들어 사용하기

-> custom hook에 대한 감이 안잡혀서.. 이김에 따라 만들어봤다

import { useContext } from "react";
import { PokemonContext } from "../contexts/PokemonContext";

const useSelectedPokemon = () => {
  const value = useContext(PokemonContext);
  if (value === undefined) {
    throw new Error("useSlectedPokemon은 PokemonStateProvier 내부에서 사용할 수 있습니다.");
  }

  return value;
};

export default useSelectedPokemon;

 

 

useContext, PokemonContext를 임포트하고 사용하는 과정과 그외에 처리해야할 것들은 커스텀훅을 사용하지 않았을 땐 state를 사용하려는 모든 컴포넌트에서 동일한 코드를 작성해줘야 한다. 하지만 커스텀훅을 사용하면  useSelectedPokemon()만 사용해 한번에 처리할 수 있다.

 

import useSelectedPokemon from "../../hooks/useSelectedPokemon";

const RemoveButton = ({ pokemon }) => {
  const [selectedPokemon, setSelectedPokemon] = useSelectedPokemon();

  const removeHandler = () => {
    const removedList = selectedPokemon.filter((selected) => selected.id !== pokemon.id);

    setSelectedPokemon(removedList);
  };
  // 생략
}

위와 같이 selectedPokemonState를 사용해야하는 모든 컴포넌트에서 useSelectedPokemon만 임포트하면 selectedPokemon, setSelectedPokemon을 사용할 수 있다