[3주차 과제] 두더지 게임 만들기#4
Conversation
soyyyyy
left a comment
There was a problem hiding this comment.
리액트가 처음이라고 하셨는데 제가 처음 했을 때 보다 잘 하시는 것 같아요... 저도 많이 어려웠는데 여러가지 고민하신 흔적이 있어서 인상깊었습니다!
지금은 App.jsx에 로직이 많이 모여 있지만, 처음에는 이렇게 쭉 써보고 어떤걸 커스텀 훅으로 분리할지, 유틸 함수로 분리할지 UI는 이렇게 컴포넌트로 빼는게 나을지 고민하는 방식으로 연습하는 방식이 도움이 많이 됐던 것 같습니다!
몇 가지 코멘트 남겼으니 확인 부탁드려요. 수고하셨습니다 💕
| @@ -0,0 +1,214 @@ | |||
| import { useCallback, useEffect, useRef, useState } from "react"; | |||
There was a problem hiding this comment.
PR에 적어주신대로 App 컴포넌트가 게임 상태, 타이머 로직, 랭킹 처리까지 너무 많은 책임을 지고 있는 것 같습니다.
게임 로직, 랭킹 저장 로직을 훅이나 유틸로 분리하고, UI 컴포넌트도 세분화하면 더 좋을 것 같아요!
|
|
||
| const RANKING_STORAGE_KEY = "mole-game-rankings"; | ||
|
|
||
| const getRandomTarget = () => { |
There was a problem hiding this comment.
랜덤 위치와 타입을 만드는 로직은 렌더링과 직접 관련된 로직이 아니기 때문에 별도로 분리한 점이 좋은 것 같습니다! 이런 부분을 util 함수로 별도의 파일에 분리해도 좋을 것 같아요!
| </Header> | ||
|
|
||
| {activeTab === "game" && ( | ||
| <Game |
There was a problem hiding this comment.
Game 컴포넌트가 받는 props가 많은 것 같습니다. 게임 안에서 게임 상태, 보드, 모달 처럼 UI 단위를 더 나누면 props도 자연스럽게 분산될 것 같습니다.
| <> | ||
| <GameLayout> | ||
| <StatusPanel> | ||
| <Card> |
There was a problem hiding this comment.
다른 곳에서 한 번 언급하긴 했는데 컴포넌트 UI를 어떻게 분리해야할지 잘 모르겠다면 우선 반복적으로 사용되는 것들부터 분리하는 연습을 하면 좋을 것 같아요! 대표적으로 button, input(textField), card 등등... 여기서도 Card가 반복적으로 사용되고 있으니 별도의 파일/컴포넌트로 분리해 props로 내용을 관리하면 좋을 것 같아요. 버튼도!
There was a problem hiding this comment.
또, Game.jsx는 상태 패널, 조작 버튼, 보드, 모달을 모두 렌더링합니다. 이 파일은 UI 중심이지만, JSX가 길어질 수 있으니 아래처럼 나누면 좋을 것 같습니다.
예를 들면 이런 느낌으로..?
game/
Game.jsx
GameStatusPanel.jsx
GameControls.jsx
GameBoard.jsx
GameOverModal.jsx
Game.styles.js
hdg0116
left a comment
There was a problem hiding this comment.
컴포넌트 분리에 대해 많은 고민을 하신 게 잘 보였어요..! 저도 처음에 어디까지 분리해야할지 고민을 많이 했는데, 역할별로 세세하게 쪼개는 연습을 하다 보니 나름대로의 기준이 생긴 것 같아요. 유민님도 역할별로 로직을 분리하는 연습을 계속 해보시는 걸 추천드려요! App.jsx에 로직을 몰아넣기보다는, 기능별로 컴포넌트 파일을 따로 분리해서 로직을 분산하는 구조가 더 좋을 것 같습니다!
너무 수고 많으셨습니다🔥
| timeLeft, | ||
| score, | ||
| successCount, | ||
| failCount, | ||
| message, | ||
| isPlaying, | ||
| isGameOver, | ||
| activeIndex, | ||
| activeType, | ||
| moleImg, | ||
| bombImg, | ||
| hitMoleImg, | ||
| onStartGame, | ||
| onStopGame, | ||
| onClickHole, | ||
| onCloseModal, |
There was a problem hiding this comment.
Game 컴포넌트가 받는 props가 많아서, 게임 화면 안에서도 상태 패널, 보드, 조작 버튼, 모달 정도로 한 번 더 나눠보면 좋을 것 같아요!
gyeongbibin
left a comment
There was a problem hiding this comment.
3주차 과제가 많이 어려웠는데, 많이 고민하면서 노력하신 흔적이 보이네요 ! 고생하셨습니다.
개선할 부분은 코멘트로 남겨두었으니 확인부탁드려요 !
| const [activeType, setActiveType] = useState(null); | ||
| const [rankingRecords, setRankingRecords] = useState([]); | ||
| const hasSaveRecordRef = useRef(false); | ||
| const [message, setMessage] = useState(""); |
There was a problem hiding this comment.
App.jsx는 전체 페이지들을 연결하는 최상위 라우팅 진입점 역할이라고 생각하시면 될 것 같습니다!
그래서 보통 App 컴포넌트에는 비즈니스 로직이나 개별 스타일을 많이 넣기보다는, 라우터·레이아웃·Provider 정도만 구성하고 최대한 가볍게 유지하는 방향으로 구현하는 방식으로 진행해보시면 좋을 것같아요 !
🏡 과제 명세
💡 기본 과제
헤더
숫자 카드 짝 맞추기 게임
2 x 2로 고정한다.alert사용)2.1 게임 보드
2.2 게임 진행 상황
게임 레벨 기능
현재 시각,레벨,클리어 시간이 포함된다. (클리어 시간은 소수점 둘째 자리까지)클리어 시간오름차순이다. (빠른 기록이 위)🔥 심화 과제
Level 1:
2 x 2제한 시간 15초Level 2:
3 x 3제한 시간 20초Level 3:
4 x 4제한 시간 30초성공 시간을 추가한다. (레벨,점수,성공 시간총 3개)레벨 내림차순과점수 내림차순으로 정렬한다. (높은 레벨이 위쪽, 같은 레벨에서는 높은 점수가 위쪽으로 정렬)window.confirm()을 통해 초기화 여부를 한 번 더 묻는다.공유과제
제목: Virtual DOM이 빠르다는 말, 반은 맞고 반은 틀리다
링크 첨부 : https://velog.io/@novaris/Virtual-DOM%EC%9D%B4-%EB%B9%A0%EB%A5%B4%EB%8B%A4%EB%8A%94-%EB%A7%90-%EB%B0%98%EC%9D%80-%EB%A7%9E%EA%B3%A0-%EB%B0%98%EC%9D%80-%ED%8B%80%EB%A6%AC%EB%8B%A4
✍️ 구현 요약 및 새로 배운 점
이번 과제에서는
Context API나 전역 상태 라이브러리 없이useState,useEffect,useRef만 사용해서 두더지 게임과 랭킹 기능을 구현했습니다. 스타일링은 emotion의styled를 사용했고, 폴더 구조는 게임, 랭킹,폴더 구조
App.jsx에 모든 JSX와 스타일이 모여 있던 구조에서, 화면 역할에 따라game,ranking폴더로 분리했습니다!src/ App.jsx App.styles.jsx game/ Game.jsx Game.styles.js ranking/ Ranking.jsx Ranking.styles.js assets/게임 흐름 관리
게임은
isPlaying상태를 기준으로 시작/종료 흐름을 제어했습니다.게임 시작 시에는 점수, 성공/실패 횟수, 남은 시간, 안내 메시지, 현재 타겟 정보를 초기화한 뒤 랜덤 타겟을 보여주도록 했습니다. 게임이 종료되면 타이머를 멈추고, 보드에 남아 있는 두더지/폭탄을 숨긴 뒤 게임 종료 모달을 띄우도록 처리했습니다.
랜덤 타겟 생성
두더지와 폭탄은
getRandomTarget로직을 통해 결정되도록 했습니다.이 로직에서는 2x2 보드 중 하나의 위치를 랜덤으로 고르고, 해당 위치에 두더지 또는 폭탄 중 하나가 나오도록 했습니다. 게임 중에는 일정 시간마다 새로운 타겟이 등장하도록 했고 두더지를 맞춘 직후에는 맞은 이미지가 잠시 보여야 해서 랜덤 변경이 바로 일어나지 않도록 조절했습니다.
클릭 처리
보드 칸을 클릭했을 때 현재 타겟이 두더지인지 폭탄인지에 따라 다른 동작을 하도록 분기했습니다.
두더지를 클릭하면 점수와 성공 횟수를 증가시키고, 안내 메시지와 맞은 두더지 이미지를 잠시 보여준 뒤 다음 타겟으로 넘어가게 했습니다. 폭탄을 클릭하면 점수를 감소시키고 실패 횟수를 증가시킨 뒤, 폭탄은 바로 사라지고 잠시 후 다음 타겟이 나오도록 처리했습니다. 이 과정에서 클릭 후 안내 메시지가 타겟과 함께 자연스럽게 사라지도록 타이밍을 맞추는 부분을 고민했습니다.
타이머 처리
타이머는
useEffect에서 관리했습니다.게임이 진행 중일 때만 interval이 실행되도록 했고, 시간이 0이 되면 게임을 종료시키도록 했습니다. 처음에는 클릭이나 상태 변경에 따라 타이머나 랜덤 변경 속도가 이상해지는 문제가 있었는데, cleanup 처리를 통해 interval이 중복 실행되지 않도록 수정했습니다.
랭킹 저장
게임 종료 시 점수가 1점 이상인 경우만 랭킹에 저장되도록 했습니다. 랭킹 데이터는
localStorage에 저장했고, 랭킹 탭에서는 저장된 데이터를 불러와 점수 기준으로 정렬해 보여주도록 했습니다. 기록 초기화 버튼을 누르면 화면 상태와localStorage기록이 함께 삭제되도록 했습니다.💦 구현 과정에서 어려웠던 & 고민했던 부분
React를 처음 사용하다 보니 처음에는 코드가 어느 파일에 들어가야 하는지부터 헷갈렸어요.
기존에는 HTML, CSS, JS를 파일별로 나눠 작성했는데, React에서는 JSX 안에서 화면 구조와 동작을 함께 작성하고 emotion으로 스타일 컴포넌트를 만드는 방식이라 익숙하지 않았습니다. 특히 emotion의
styled로 만든 컴포넌트는 선언만 한다고 화면에 보이는 것이 아니라,return안에서 직접 태그처럼 사용해야 한다는 점을 이해하는 데 시간이 걸렸습니다..!처음에는 대부분의 상태, 함수, JSX를
App.jsx에 작성해서 기능 흐름을 파악하기는 쉬웠지만, 코드가 길어지면서 어떤 기준으로 파일을 나누어야 할지 고민이 되었습니다. 컴포넌트 분리 개념은 이론으로는 이해했지만, 실제 코드에서는 어디까지를 하나의 컴포넌트로 봐야 하는지 감이 잘 잡히지 않았어요. 우선 화면 역할을 기준으로Game,Ranking컴포넌트를 분리했고, 스타일도 각 화면별 styles 파일로 나누었습니다. 이후에는 게임 로직을 custom hook으로 분리하는 방향도 고민해보고 싶습니다.랭킹이 두 번씩 저장되는 문제가 있었습니다. React state는 값을 변경해도 즉시 반영되지 않기 때문에, 중복 저장 여부를 useState가 아니라 useRef로 관리하도록 수정했습니다.
이 과정을 통해 화면에 보여줄 값은 useState, 렌더링과 직접 관련 없지만 즉시 기억해야 하는 값은 useRef를 사용하는 것이 적절하다는 점을 배웠습니다.!
랜덤으로 두더지와 폭탄을 보여주는 부분도 고민이 많았습니다.
처음에는 클릭해야만 다음 이미지가 나타나는 구조였는데, 실제 두더지 게임처럼 클릭하지 않아도 일정 시간마다 자동으로 타겟이 바뀌어야 자연스럽다는 것을 깨달았습니다.
또 두더지를 클릭했을 때 바로 다음 타겟으로 넘어가면 맞은 이미지가 보이지 않아서,
activeType을 "hit"으로 잠시 바꾸고setTimeout으로 700ms 후 다음 타겟이 나오도록 구현했습니다.폭탄을 클릭했을 때는 자동 타겟 변경 타이머와 클릭 시 즉시 변경 로직이 겹쳐서 화면이 너무 빨리 바뀌는 것처럼 느껴졌습니다. 그래서 폭탄 클릭 시에는 먼저 현재 타겟을 비우고, 짧은 시간 후 다음 타겟이 나오도록 조정했습니다.
🙋♀️ 리뷰 요청 포인트 & 질문
emotion styled 컴포넌트 사용 방식이 과하거나 어색한 부분은 없는지 확인받고 싶습니다!
외부 UI 라이브러리 없이 직접 UI 컴포넌트는 구현했지만 아직 컴포넌트 분리는 충분히 하지 못했습니다..! 현재 코드 구조가 제가 봐도 복잡한 상태인거같아요.. 컴포넌트 분리 관련 개념이 이론으로 들었을때는 이해가 되었는데 막상 직접 구현하려고 하니 어떤 기준으로 컴포넌트를 나누어야 할지 감이 안 잡히더라고요......... (컴포넌트 관련 피드백 주시면 코드 리뷰 반영때 열심히 고쳐보겠습니다!!!!)
코드 리뷰를 반영하면서 컴포넌트 분리를 꼭 시도해보고 싶은데, 현재 코드 기준으로 어떤 단위로 파일을 나누면 좋을지 피드백 받고 싶습니다!
localStorage에 랭킹 데이터를 저장하고 불러오는 로직이 적절한지 궁금합니다!📷 결과물
2026-05-01.11.23.46.mov