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 9 commits into
base: main
Choose a base branch
from

Conversation

look-back-luca
Copy link
Contributor

@look-back-luca look-back-luca commented Nov 5, 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이 위쪽으로, 같은 레벨 중에선 플레이 시간이 짧은게 위쪽으로 정렬

공유과제

제목: 리액트 Hooks - useState, useEffect

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


❗️ 내가 새로 알게 된 점

  1. JSX 안에서는 세미콜론 필요 없음. → css 스타일 지정 / 자바스크립트 코드 쓸 때는 예외
  2. export 할 때는 default가 있냐 없냐에 따라서 import 하는 방식 달라짐
  3. default 넣으면 하나 / 안 넣으면 여러 개 → { } 써서 import 해야 함
  4. 반드시 import 할 때 파일 경로 따옴표 ‘./~.jsx’ 로 감싸기 !
  5. 커스텀 태그는 리액트 컴포넌트로 인식되기 때문에 HTML 요소로 변환하는 과정이 필요하다 → 이번 과제에서는 스타일링 라이브러리를 사용했음
  6. 컴포넌트 안에 내가 쓴 모든 커스텀 태그에 대한 컴포넌트 정의를 해주어야 함. 안 그러면 reference 오류 발생
  7. useEffect 훅에서 빈 배열 []을 넣어 주면, 컴포넌트가 처음 렌더링될 때 한 번만 실행된다.
  8. → React에서는 함수형 컴포넌트 내에 필요한 함수를 변수처럼 정의하는 방식으로 작성한다. 이런 함수들은 컴포넌트가 렌더링될 때 함께 생성되고, 컴포넌트 내부에서만 사용할 수 있는 "지역 함수"처럼 동작함.
  9. DOM 조작 대신 React 방식 사용: React에서는 document.createElement와 같은 DOM 조작을 직접 사용하지 않고, JSX를 통해 렌더링한다.

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

버튼 안에 텍스트 정렬이 중앙으로 안되는 문제가 생겼다.
버튼 크기를 padding으로 조정해서 그랬다. flex 속성을 줘서 가로축, 세로축 정렬을 하니까 해결되었다.

레벨에 따라 게임이 변화하는 부분은 더 생각해보아야 할 것 같다.


🥲 소요 시간

  • 6일 + @@

🖼️ 구현 결과물

https://www.youtube.com/watch?v=7bBBZkiCc3Y

Copy link

@heesunee heesunee left a comment

Choose a reason for hiding this comment

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

우선 너무너무 고생많으셨습니다 😂 아무래도 시험기간도 겹치고, 리액트 과제도 처음인데 카드 게임 구현을 위한 여러 요구사항이 많다보니 힘드셨을 것 같아요 ㅠㅠ
그럼에도 불구하고 함수 네이밍, props 값 전달, 랭킹/게임 모드 구분 렌더링 등 꼼꼼하게 해주신 것 같아서 코드 읽는데도 이해가 잘 갔던 것 같습니다 ㅎㅎ

리팩토링때는

  • 중복 코드는 재사용 가능한 구조로 바꾸기
  • theme.js
  • 폴더 구조 개선
    을 중점으로 하시면 좋을 것 같아요 ! 고생많으셨고 앞으로도 화이팅 해봅시당 💪💪💪

Copy link

Choose a reason for hiding this comment

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

사용하지 않는 파일들은 삭제해도 될 것 같습니다 !

const hours = date.getHours();
const minutes = date.getMinutes();

const timeNow = `${year}년 ${month}월 ${day}일 ${hours}시 ${minutes}분`;
Copy link

@heesunee heesunee Nov 8, 2024

Choose a reason for hiding this comment

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

게임 플레이 시간 저장부분에서, 저는 toLocaleString()을 사용해서 간단하게 저장했습니다! 한번 찾아보시는 것도 좋을 것 같아요 :)

Copy link

Choose a reason for hiding this comment

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

그리고 첨언하자면, 시간을 뽑아내어 원하는 형태로 포맷팅 하는 함수를 작성해보는 연습을 해보는 것도 좋을 것 같아요! 이는 반복적인 코드가 나타나는 것을 줄이기 위함인데, 추후 유용하게 사용할 수 있을겁니다 !

const [timer, resetTimer] = useTimer(isTimerActive);

const [isRankingView, setIsRankingView] = useState(false);
const [isGameView, setIsGameView] = useState(true);
Copy link

Choose a reason for hiding this comment

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

게임모드와 랭킹모드 둘 다 선언이 되어있는는데, 게임 모드일때는 랭킹 모드가 아니고, 랭킹 모드일때는 게임모드가 아닌 관계라서, 저는 랭킹모드 하나만 선언해서 RankingMode일때는 게임 화면이 렌더링이 되게 설정했습니다! 이 부분 좀 더 간단하게 관리할 수 있을 것 같아요 : )

Copy link

Choose a reason for hiding this comment

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

희선님이 잘 지적해주셨네요! 가능하면 state는 적게 사용하면 좋아요!

특히 isRankingView <-> isGameView 는 서로 반대 관계이고,
isGameStarted ~= isTimerActive 는 서로 비슷한 관계이니까

하나의 state로 표현할 수 있을 것 같아요! 그럼 state가 4개에서 2개로 줄어들겠죠?

{isRankingView && (
<Ranking updateRanking={updateRanking} />
)}
</>
Copy link

@heesunee heesunee Nov 8, 2024

Choose a reason for hiding this comment

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

위에서도 게임과 랭킹모드가 상호 배타적인 관계라고 말씀 드렸었는데, 예를 들어서
{isRankingMode ? () : () } 이런식으로 조건부 렌더링하면 좀 더 간결해 질 수 도 있을 것 같아요

initializeGame(); // 게임 재초기화
}
}
};
Copy link

@heesunee heesunee Nov 8, 2024

Choose a reason for hiding this comment

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

저는 카드를 섞는 부분, 카드를 선택했을 때 실행되는 함수같은건 utils 폴더에 gameLogic.js 파일로 분리해서 관리했습니다! 그렇게 하면 훨씬 컴포넌트 내의 코드가 더 깔끔해지더라구요, 지금은 레벨이 하나지만 후에 레벨을 여러 선택하는 기능까지 추가되면 로직과 UI를 분리해보시는 것도 좋을 것 같아요 :)

`
const TableRow = styled.tr`
background-color: #fff;
&:nth-child(even) {
Copy link

Choose a reason for hiding this comment

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

줄별로 색 다르게 하는 부분 저는 테이블 셀 순서 가져와서, %2 ===0 이면 짝수, 홀수로 해서 처리하려다가 말았는데 nth-child 라는 아주 간단한 방법으로 할 수 있었네요.. 알아갑니다 !!! ㅎㅎ

Copy link

Choose a reason for hiding this comment

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

timeDisplay가 실제로 사용되는 부분이 있나요?

Copy link

Choose a reason for hiding this comment

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

components 폴더는 보통 UI와 관련된 컴포넌트들을 주고, timer과 같은 커스텀 훅은 UI와 관련된 부분이 아니라 로직 중심이기 때문에 UI 컴포넌트들과는 별도로 hooks 폴더나 utils 폴더에 위치시키는 게 일반적인 것 같습니다!

Copy link

Choose a reason for hiding this comment

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

폴더 구조에 따라 다를 수 있긴 한데, timer.jsx를 components에 위치시키는 건 확실히 일반적이진 않은 것 같아요 ! hook을 위한 폴더 안에 넣어두곤 하니 참고 부탁드립니다~ 그리고 이런 훅들은 태그를 반환하지 않으므로 .jsx가 아닌 .js를 써서 더욱 명확히 하곤 합니다 !

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
Copy link

Choose a reason for hiding this comment

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

ko로 설정하기!

<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
Copy link

Choose a reason for hiding this comment

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

페이지의 이름을 별도로 (1 to 50) 설정해주면 좋을 것 같아요

Copy link

@ocahs9 ocahs9 left a comment

Choose a reason for hiding this comment

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

고생 많으셨습니다 ~!
처음이라 정말 쉽지 않았을텐데 심화 과제까지 시도한 모습 보니 너무 훌륭하네요 👍

희선님이 좋은 코멘트 듬뿍 해주셔서 제가 할 말은 많지 않았지만, 제 코멘트도 한번 확인해주시고 추가 공부할 수 있는 시간 가져보면 좋을 것 같아요! 이렇게 해나아가다보면 금방 실력 늘겁니다 화이팅 !!!!!!

Copy link

Choose a reason for hiding this comment

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

안 사용하는 파일은 가능하면 제거해주세요~!

const [timer, resetTimer] = useTimer(isTimerActive);

const [isRankingView, setIsRankingView] = useState(false);
const [isGameView, setIsGameView] = useState(true);
Copy link

Choose a reason for hiding this comment

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

희선님이 잘 지적해주셨네요! 가능하면 state는 적게 사용하면 좋아요!

특히 isRankingView <-> isGameView 는 서로 반대 관계이고,
isGameStarted ~= isTimerActive 는 서로 비슷한 관계이니까

하나의 state로 표현할 수 있을 것 같아요! 그럼 state가 4개에서 2개로 줄어들겠죠?

const hours = date.getHours();
const minutes = date.getMinutes();

const timeNow = `${year}년 ${month}월 ${day}일 ${hours}시 ${minutes}분`;
Copy link

Choose a reason for hiding this comment

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

그리고 첨언하자면, 시간을 뽑아내어 원하는 형태로 포맷팅 하는 함수를 작성해보는 연습을 해보는 것도 좋을 것 같아요! 이는 반복적인 코드가 나타나는 것을 줄이기 위함인데, 추후 유용하게 사용할 수 있을겁니다 !

Comment on lines +68 to +91
const handleLevelChange = (newLevel) => {
setGameLevel(newLevel);
setIsGameStarted(false);
resetTimer();
setIsTimerActive(false);
};

const handleGameButtonClick = () => {
setIsGameView(true);
setIsRankingView(false);
}

const handleRankingButtonClick = () => {
setIsGameView(false);
setIsGameStarted(false);
setIsTimerActive(false);
resetTimer();
setIsRankingView(true);
};

const handleResetRanking = () => {
localStorage.removeItem("gameData");
setUpdateRanking(prev => !prev);
};
Copy link

Choose a reason for hiding this comment

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

이 부분, 완전히 중복되는 코드는 아니긴 하지만 일정 부분은 계속 반복되지 않나요?
그럴 때는 초기화를 위한 함수를 따로 만들어보는 건 어떤가요? 이게 위에서 첨언한 함수의 재사용과도 연관이 있어요!
여유가 되면 한번 고민해보는 것도 좋을 것 같습니다 !

import { useState, useEffect } from "react";
import useTimer from "./timer.jsx"; // 타이머 기능의 커스텀 훅

const Game = ({ isGameStarted, onGameStart, onGameEnd, setTimer, gameLevel }) => {
Copy link

Choose a reason for hiding this comment

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

사용하지 않는 인자는 없애주세요! 나중에 유지/보수할 때 힘들어집니다~~

Comment on lines +15 to +26
{onResetRanking && (
<ResetButton onClick={onResetRanking}>초기화</ResetButton>
)}
{!onResetRanking && (
<>
<GameStage value={gameLevel} onChange={handleLevelChange}>
<LevelOne value={1}>Level 1</LevelOne>
<LevelTwo value={2}>Level 2</LevelTwo>
<LevelThree value={3}>Level 3</LevelThree>
</GameStage>
<StopWatch>소요 시간: {timer.toFixed(2)}초</StopWatch>
</> )}
Copy link

Choose a reason for hiding this comment

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

이렇게 해도 되긴 하지만, 삼항 연산자를 이용해서 렌더링해봅시다!
onResetRanking ? <> : <> 방식으로 하면 훨씬 가독성도 좋고, 불필요한 코드의 양도 줄어들 것 같아요!

const GameRanking = styled.button`
background-color: #ECDFCC;
border-radius: 10%;
`;
Copy link

Choose a reason for hiding this comment

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

공통 스타일까지는 아직 이른 것 같아서 말씀드리지 않을까 했는데, 희선님이 잘 지적해주셨네요! 나중에는 계속 반복되는 스타일도 하나로 만든 뒤, 자식 스타일들을 만드는 방법이 있으니 참고해두었다가 추후 여유가 될 때 한번 시도해보는 것도 좋을 것 같습니다 !

margin-left: auto;
background-color: #ECDFCC;
border-radius: 10%;
`;
Copy link

Choose a reason for hiding this comment

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

이번 4주차 과제에서 theme 사용해봐야하니까 희선님이 언급해주신 것 꼭 살펴보세요 !!
굿굿

Copy link

Choose a reason for hiding this comment

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

폴더 구조에 따라 다를 수 있긴 한데, timer.jsx를 components에 위치시키는 건 확실히 일반적이진 않은 것 같아요 ! hook을 위한 폴더 안에 넣어두곤 하니 참고 부탁드립니다~ 그리고 이런 훅들은 태그를 반환하지 않으므로 .jsx가 아닌 .js를 써서 더욱 명확히 하곤 합니다 !

</tbody>
</Table>
);
}
Copy link

Choose a reason for hiding this comment

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

그리고 00:00과 같은 형식으로 포맷팅하려면, .padStart 와 같은 함수를 사용하곤 하니 한번 찾아보는 것도 좋을 것 같습니다 !

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String/padStart

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

Successfully merging this pull request may close these issues.

3 participants