Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3주차 기본/심화/공유 과제] 1 to 50 게임 #4

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

thisishwarang
Copy link
Contributor

@thisishwarang thisishwarang commented Nov 4, 2024

✨ 구현 기능 명세

💡 기본 과제

  • Context API, 전역상태 라이브러리 사용 X (ThemeProvider 제외)
  1. 헤더
  • 게임/랭킹 2개의 메뉴 선택 가능
  • 게임 선택 시 헤더 우측에 레벨 선택 Select와 타이머 표시
  • 게임 선택 시 게임 판 출력
  • 랭킹 선택 시 헤더 우측엔 아무것도 나오지 않음
  • 랭킹 선택 시 랭킹 보드 출력
  1. 게임
  • (기본) 한 종류의 레벨만 구현
  • 숫자는 항상 랜덤으로 표시됨. (초기 표시 숫자들도, 이후 열리는 숫자들도 모두 랜덤)
  • 처음에 표시되는 숫자는 클릭해야 하는 숫자의 앞에 절반임. 만약 level 1이라 118까지 클릭해야한다면, 처음에는 19까지의 숫자가 랜덤으로 보여짐
  • 게임판 위쪽에 다음으로 클릭해야할 숫자를 표시
  • 1을 누르는 순간 게임이 시작되며 헤더 우측의 타이머가 동작. 타이머는 소수점 2번째 자리까지 측정.
  • 마지막 숫자 클릭시 게임 종료
  • 게임 종료 시, 타이머를 멈추고 alert 창을 띄워주며 걸린 시간을 표시
  • 게임 종료 시, 현재 시각, 게임의 레벨, 플레이 시간 3개의 정보를 localStorage에 저장 (랭킹에서 사용)
  • 종료 창에서 확인 누르면 다시 시작할 수 있는 상태로 게임 초기화
  • 게임 중 level 변경 시 다시 시작할 수 있는 상태로 게임 초기화
  1. 랭킹
  • localStorage에서 데이터 불러오기
  • 플레이 시간 오름차순으로 보여야 함 (빨리 깬 기록이 위쪽)
  • 우측 상단의 초기화 버튼 누르면 대시보드 초기화 (localStorage도 초기화)

🔥 심화 과제

  1. 게임
  • Level 선택 가능
    Level 1: 3 x 3, Level 2: 4 x 4, Level 3: 5 x 5
  • 숫자 클릭할 때 클릭되는 것 같은 효과 (예시: 깜빡거림)
  • 게임 종료 alert 대신, React의 createPortal을 사용하여 Modal 구현
    createPortal
  1. 랭킹
  • Level 내림차순 & 시간 오름차순 정렬(정렬 기준이 2개). 높은 Level이 위쪽으로, 같은 레벨 중에선 플레이 시간이 짧은게 위쪽으로 정렬

공유과제

제목: useEffect, 클린업 함수, useLayoutEffect, useRef

링크 첨부 : https://wave-web.tistory.com/95


❗️ 내가 새로 알게 된 점

  • 카드를 클릭했을 시 효과를 주기 위해 우선 각 카드에 id값으로 card-${number} 를 줬습니다. 이후 클릭했을 때 해당 id를 가진 카드에게 clicked라는 클래스를 추가해주었습니다.
    이때 레벨을 바꿔서 새로운 게임을 만들거나, 게임을 끝까지 완료한 후 새로운 게임을 만들 때 이를 초기화 하는 과정에서 모든 카드에서 clicked라는 클래스를 제거하기 위해
const clickedCards = document.querySelectorAll("[id^='card-']");

이러한 방식으로 모든 카드들을 선택하고 clicked라는 클래스를 지워줬습니다. 학교 수업에서나 배웠던 ^= 선택자를 사용해보니 좀 신기하네요.

  • emotion/react 라이브러리를 사용하여 스타일링을 했는데 ~~.style.js 파일에서 정의한 스타일을 jsx파일에서 가져와 각 태그의 css prop으로 전달하려고 하니 에러가 났습니다. react + vite + ts 를 사용한 다른 프로젝트에서는 별다른 설정없이 @emotion/react 라이브러리만으로 가능했는데 왜인지 모르겠지만 js환경에서는 에러가 발생해서 @emotion/babel-plugin 라이브러리를 따로 설치하여 vite.config.js 파일에서 설정해줬더니 에러를 해결하긴 했습니다.

❓ 구현 과정에서의 어려웠던/고민했던 부분

  • 컴포넌트, 커스텀훅과 같은 것들을 다른 파일에서 import할 때 경로를 @로 시작하는 절대경로로 가져오기 위해서 jsconfig.json, vite.config.js 파일 등을 수정하는 과정에서 여러가지 어려움이 많았습니다. 두 파일 중 하나의 파일에서 뭔가 잘못하면 경로설정이 안되면서 에러가 뜨고, 현재는 절대경로로 호출이 가능은 하지만 자동완성이 잘 안돼서 완전하지 못한 것 같아요. 좀 찾아보니 typescript로는 vite-tsconfig-paths 와 같은 라이브러리를 사용하는 등의 방식으로 구현이 가능한데 javascript로는 한계가 좀 있는것 같습니다.

  • 게임을 실행하여 현재 타이머가 흘러가고 있는 도중에 레벨을 변경하면 타이머도 초기화 되고, 새로운 게임이 생성되도록 구현하는 부분에 있어서 level이 변경되는것을 커스텀훅에서 감지는 하지만 리렌더링이 일어나지 않는 부분에서 어려움이 있었습니다.
    현재는 useEffect를 사용하여 level의 변화를 감지하고, 게임 및 시간을 초기화 시켜주는 로직을 넣어놨는데 다른 방법이 있을지 궁금합니다..

  • 디자이너가 준 피그마 없이 하려니까 참 어렵네요


🥲 소요 시간

  • 30h

🖼️ 구현 결과물

과제 구현 결과물

Copy link

@Minn-Choi Minn-Choi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드가 너무 깔끔하고 컴포넌트랑 훅이 잘 나눠져있어서 보기 너무 편했어요🤩🤩
코드가 작동하는 것도 중요하지만 이렇게 코드를 가독성있게 잘 나누는 것도 정말 중요하구나를 다시 한번 느끼고 갑니댜....🌟
수고하셨습니다 !!!

const Button = ({ variant, isSelected, onClick, customStyle, children }) => {
return (
<button
css={[buttonStyle(isSelected), variantStyle[variant], customStyle]}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요기서는 isSelected이고

import { css } from "@emotion/react";
import { Theme } from "../../styles/theme";

export const buttonStyle = (isClicked) => css`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요기서는 isClicked인 이유가 있을까용?

@@ -0,0 +1,37 @@
import useMakeGame from "@hooks/useMakeGame";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

훅으로 분리해서 이렇게 코드가 깔끔한거군뇨,, 짱 깔끔해요 ..!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호,, 훅도 따로 파일을 만들어서 관리할 수 있군요,,
이렇게 따로 떼는건 한 파일에서 너무 복잡해질때만 쓰면 될까요??

Comment on lines +21 to +29
{boardNumbers.map((number, i) => (
<NumberCard
key={i}
id={`card-${number}`}
onClick={() => handleNumberClick(number)}
>
{number}
</NumberCard>
))}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i를 key로 삼지 않고 number를 사용하면 안되려나용?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

카드의 몇번째 요소인지를 key로 삼으려고 하신것일까요?!

Comment on lines +4 to +6
const shuffleArray = (array) => {
return array.sort(() => Math.random() - 0.5);
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

영경이 코드에도 작성하긴 했는데
저도 sort()를 사용해서 랜덤하게 섞긴 했지만 Fisher-Yates Shuffle을 써서 구현하는 방법도 있다고 해요오
배열의 마지막 요소부터 순차적으로 무작위 위치에 있는 요소랑 교환해가지고 섞는 방법인데 배열의 크기가 커져도 효율적이고 더 빠르고 무작위성을 보장한다고 합니댜!

Copy link

@bykbyk0401 bykbyk0401 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

역할과 파일별로 분리가 너무 잘 되어있어서 파악하기 너무 편리했습니다!
이렇게 잘 나눠서 작성할 수 있는지 몰라서 저는 뭉터기로 코드를 짠 것 같은데 보기 힘드실 것 같아 죄송하네요..
어떻게 하면 좋은 구조와 효율적으로 구현할 수 있을지 보면서 많이 생각하고 배울 수 있었습니당👍
다음에는 Thmemprovider나 Global 등 스타일도 적용시켜 봐야겠어요~!!

이번 과제도 너무너무 수고하셨습니다

import { Global, ThemeProvider } from "@emotion/react";
import GlobalStyle from "./styles/global";
import { Theme } from "./styles/theme";
import Home from "@pages/Home/Home";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global과 ThemeProvider를 이렇게 사용한다는 걸 알 수 있었어요!!!

리액트에서 페이지와 컴포넌트를 @pages, @components로 받아오는 건가요???
찾아보니까 라우터..? 랑은 다른 것 같은데 파일 구조로 import하지 않고 이렇게 받아오는 이유가 궁금합니다

return shuffleArray(numbers);
};

const useMakeGame = (level, time, handleTimeChange) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 레벨별로 칸 수나 종료 숫자 등을 각각 지정해줬는데, level+2 나 row를 이용해 사이즈를 지정해서 동적으로 적용시키는게 좋은 것 같아요.
저는 문제 의도와는 다르게 정적으로 값을 할당해버린 것 같네요ㅠㅠ

clickedCard.classList.remove(`card-${currentNumber}`);
setCurrentNumber(currentNumber + 1);
}
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

레벨별로 숫자에 따른 처리도 이렇게 생각할 수 있다는걸 배워갑니당

Comment on lines +21 to +29
{boardNumbers.map((number, i) => (
<NumberCard
key={i}
id={`card-${number}`}
onClick={() => handleNumberClick(number)}
>
{number}
</NumberCard>
))}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

카드의 몇번째 요소인지를 key로 삼으려고 하신것일까요?!

@@ -0,0 +1,37 @@
import useMakeGame from "@hooks/useMakeGame";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호,, 훅도 따로 파일을 만들어서 관리할 수 있군요,,
이렇게 따로 떼는건 한 파일에서 너무 복잡해질때만 쓰면 될까요??

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants