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주차 기본/심화/공유 과제] React 연습 #6

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

Conversation

Minn-Choi
Copy link
Contributor

✨ 구현 기능 명세

💡 기본 과제

  • 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이 위쪽으로, 같은 레벨 중에선 플레이 시간이 짧은게 위쪽으로 정렬

공유과제

제목: React에서의 '상태관리' 😎

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


❗️ 내가 새로 알게 된 점

  • 유사 배열 객체나 iterable 객체를 배열로 변환하는데 Array.from()을 사용 !
  • sort()를 통해서 배열 섞기 !
    const mixNumbers = initialNumbers.sort(() => Math.random() - 0.5);
    예를 들어보자면 initialNumbers가 [1, 2, 3, 4, 5, 6, 7, 8, 9]일 때, 만약 Math.random()이 밑에처럼 값을 반환했으면
    1: Math.random() → 0.3 (0.3 - 0.5 = -0.2) → 순서 유지
    2: Math.random() → 0.7 (0.7 - 0.5 = 0.2) → 2가 3 앞에
    3: Math.random() → 0.1 (0.1 - 0.5 = -0.4) → 1이 4 앞에
    ...
    이렇게 반복적으로 요소들이 비교되면서 무작위로 섞이게 되는 로직이라고 합니다..!
    그런데 뭔가 더 쉽고 간단한 random방법이 있을 것 같아요..😢
  • React의 createPortal을 처음 사용해봤습니다 !
    일반적으로 React 컴포넌트는 부모 컴포넌트의 DOM 트리 구조에 따라 렌더링되는데,
    createPortal을 사용하면, 특정 컴포넌트를 부모 컴포넌트의 DOM 트리와는 별도로 다른 위치에 렌더링할 수 있고 부모 요소의 스타일이나 레이아웃과 무관하게 Modal 같은 요소를 구현할 수 있다고 합니당 !

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

  • globalStyle을 한번 작성해봤는데 정확히 무슨 내용이 들어가야하는지.. 어떻게 써야하는지.. 아직은 잘 모르겠어요 ! 웹 심화스터디 다시 복습하면서 천천히 작성해보겠습니다 ㅠㅠ!
  • 우선 카드 배열을 어떻게 만들지부터 , 랜덤으로 어떻게 나오게하지, 컴포넌트는 어떻게 나눠야할지 고민을 많이 했는데 기능 구현하는데도 시간이 오래걸려서 코드의 가독성이나 컴포넌트 분리를 딱 보기 좋게 한 건 아닌 거 같아 아쉽습니댭..
  • level2, level3도 구현 시도를 해봤었는데 '다음 숫자' 이렇게 뜨는 부분이나 타이머 뜨는 부분 등 뭔가 level을 옮겨도 분리가 안되고 배열도 이상해서 완성하지 못했는데 추후 디벨롭 꼭 해볼게요 !!

🥲 소요 시간

  • 7d

🖼️ 구현 결과물

3.1.mp4
3.2.mp4

@Minn-Choi Minn-Choi linked an issue Nov 5, 2024 that may be closed by this pull request
@Minn-Choi Minn-Choi added ❤️최민 최민 🌳과제 과제입니다 💫Feature 새로운 기능 구현입니다 🐻Design 디자인 추가입니다 labels Nov 5, 2024
@@ -0,0 +1,42 @@
#root {

Choose a reason for hiding this comment

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

프로젝트 처음 세팅을 할때 기본으로 먹혀있는 이런 App.css, index.css 파일은 삭제해서 스타일을 초기화 시켜주고, 시작하는게 좋습니다! 추가적인 스타일을 먹였을때 예상하지 못한대로 동작할 수 있기 때문이예요!

Choose a reason for hiding this comment

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

오호.. 저도 처음에 이 파일이 있는지 모르고 원하는 바와 다르게 화면이 나와서 root를 어디서 설정하고 있는지 한참 찾았거든요..
삭제한다는 생각은 못하고 여기서 수정을 해줬는데 앞으로는 삭제하고 구성해나가야겠어요!!

@@ -0,0 +1,68 @@
:root {

Choose a reason for hiding this comment

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

여기도 App.css 와 마찬가지로 삭제하고 시작하면 좋습니다!

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">

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>

Choose a reason for hiding this comment

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

제목도 설정하면 좀 더 좋을거 같아요~

return (
<>
<GlobalStyle />
{/* <ProfileCard /> */}

Choose a reason for hiding this comment

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

사용하지 않는 주석들은 삭제하기!


return (
<S.PageContainer>
<Header time={time} activeButton={activeButton} setActiveButton={setActiveButton} />

Choose a reason for hiding this comment

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

여기서 Header 컴포넌트로 setActiveButton이라는 setter함수 자체를 보내주고 있는데 이런식으로 자식 요소에게 부모의 state 값을 즉각적으로 변경할 수 있는 setter 함수를 위임하는것이 좋지 않다고 합니다. 이는 자식 컴포넌트에게 외부 데이터 변경의 주도권을 넘겨주는 것과 같은 작동방식이기 때문에 handleActiveButtonChange 함수를 만들고 내부에서 setActiceButton으로 값을 관리하고, Header 컴포넌트에게는 이 함수를 넘겨주는것이 좀 더 건강한 로직이 될 수 있어 보여요!

Choose a reason for hiding this comment

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

아.. 큰일났다...😱

setTime((prevTime) => parseFloat((prevTime + 0.01).toFixed(2)));
}, 10);
}
return () => clearInterval(timer);

Choose a reason for hiding this comment

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

클린업 함수로 clearInterval 좋네요!!

import React, { useState } from 'react';
import * as S from "./styled";

function Header({ activeButton, setActiveButton, time, resetTimer }) {

Choose a reason for hiding this comment

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

왜 화살표 함수로 컴포넌트를 생성하지 않고 function으로 했는지 알 수 있을까요??

@@ -0,0 +1,177 @@
import styled from '@emotion/styled';

Choose a reason for hiding this comment

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

하나의 스타일 파일에 header, modal 등등 games 폴더에 있는 모든 컴포넌트들의 스타일을 정의하다보니 가독성이 떨어지고 어떤 스타일을 먹였는지 이름을 찾기가 조금 어려운 단점이 있는것 같아요. 하나의 컴포넌트와 그에 적용되는 스타일 파일은 하나씩 만들어서 같은 폴더에 넣어주면 좀 더 가독성도 좋아지고, 폴더구조도 깔끔해 질 것 같습니다!

import styled from '@emotion/styled';

//header.jsx
export const Header = styled.div`

Choose a reason for hiding this comment

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

Header컴포넌트의 가장 바깥 레이아웃이 되는 이 스타일에는 div대신 header 태그를 사용하면 시맨틱 태그의 장점을 살릴 수 있을것 같아요!

Comment on lines +30 to +55
export const ButtonGame = styled.button`
padding: 0.5rem 1rem;
font-size: 1rem;
cursor: pointer;
border-radius: 0.5rem;
border:0;
width: 5rem;
background-color: ${({ isActive }) => (isActive ? 'beige' : 'pink')};

&:focus {
outline: none;
}
`;

export const ButtonRanking = styled.button`
padding: 0.5rem 1rem;
font-size: 1rem;
border-radius: 0.5rem;
cursor: pointer;
width: 5rem;
background-color: ${({ isActive }) => (isActive ? 'beige' : 'pink')};

&:focus {
outline: none;
}
`;

Choose a reason for hiding this comment

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

이 두개의 버튼 스타일은 하나만 만들고 게임, 랭킹에 공통으로 줘도 괜찮을거 같아요!

}
`;

export const Dropdown = styled.div`

Choose a reason for hiding this comment

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

이걸 styled.select`` 로 줘도 될 것 같아요!

const [currentLevel, setCurrentLevel] = useState(1);

const startCards = () => {
const initialNumbers = Array.from({ length: 9 }, (_, i) => i + 1);

Choose a reason for hiding this comment

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

Level2,3을 구현할때 level의 value값을 prop으로 받아서 +2를 해주면 카드 배열의 한 줄 개수를 알 수 있고, 이를 제곱하면 카드의 총 개수를 알 수 있으며, 여기서 *2 해주면 필요한 총 숫자 개수까지 알 수 있을 것 같아요. 이걸 변수로 설정해서 length: 9 이 부분을 동적으로 설정해줄 수 있겠네요!

Choose a reason for hiding this comment

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

threeGrid, createNewCard 함수 등 Level1의 개수와 관련된 부분을 모두 이런 방식으로 수정해주면 될 것 같습니다!

Comment on lines +29 to +31
do {
randomAfterNumber = Math.floor(Math.random() * 9) + 10;
} while (usedNumbers.has(randomAfterNumber));

Choose a reason for hiding this comment

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

do while 문을 실제로 쓴건 처음보네요. while문과의 차이가 무조건 do {} 부분을 한번 실행하고 반복을 결정한다고 하는데 의도하신 부분일까요?

Comment on lines +74 to +81
const currentTime = new Date().toLocaleString();
const existingData = JSON.parse(localStorage.getItem('gameData')) || [];
existingData.push({
currentTime,
level: currentLevel,
playTime: time.toFixed(2)
});
localStorage.setItem('gameData', JSON.stringify(existingData));

Choose a reason for hiding this comment

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

모달을 닫는 함수에 있어서 로컬스토리지에 시간 같은 데이터를 저장하는 로직은 필요한 로직이지만 약간 분리시켜주는게 좋을 것 같아요. util함수로 분리하면 코드도 훨씬 깔끔해지고, 도메인을 분리할 수 있을 것 같습니다!

display: flex;
align-items: center;
justify-content: center;
z-index: 1000;

Choose a reason for hiding this comment

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

z-index의 값을 1000까지 안줘도 괜찮습니다. 나중에 z-index로 관리해야할 요소들이 많아질수록 쌓임 맥락이 점점 헷갈리기 때문에 예를들면 모달 백드롭 부분을 z-index: 1, 모달 컨텐츠 요소를 z-index: 2로 설정해주는것이 좋을 것 같습니다!

Comment on lines +122 to +125
padding: 20px;
border-radius: 8px;
text-align: center;
max-width: 400px;

Choose a reason for hiding this comment

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

다른 부분에선 rem단위를 쓰셨는데 여기만 px로 하신 이유가 있으신가요? 없다면 통일하면 좋을 것 같아요!

Copy link

@thisishwarang thisishwarang left a comment

Choose a reason for hiding this comment

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

과제 너무 고생 많으셨습니다! 스타일링이 디테일하고 파일구조를 분리하려고 많이 노력하신것 같아요! 다만 코드리뷰에도 적었는데 하나의 스타일 파일에 너무 많은 컴포넌트들의 스타일을 주기 보다 하나의 컴포넌트마다 하나의 스타일 파일을 만들어주면 좋을 것 같습니다!

나머지 궁금한 점들은 코드리뷰에 남겨놨으니 확인해주세요!!

@thisishwarang
Copy link

thisishwarang commented Nov 9, 2024

그리고 지금 실행화면에서 1부터 9까지 클릭한 이후 10부터 18 까지의 숫자들은 클릭했을때 카드 자체가 없어져야 하는데 이건 어떻게 하면 좋을지도 생각해보면 좋겠네요!

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.

이번 과제를 하면서 제가 효율적이지 않은 돌아가기만 하는 코드를 짜고있는것 같다는 생각은 들었지만,,
민이가 구현한 코드를 살펴보면서 정말정말 수정해야 할 부분이 많다는게 느껴지네요...
리액트 감을 익히는데 많은 도움이 되었숩니다~~~
이번주도 수고하셨어용

@@ -0,0 +1,42 @@
#root {

Choose a reason for hiding this comment

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

오호.. 저도 처음에 이 파일이 있는지 모르고 원하는 바와 다르게 화면이 나와서 root를 어디서 설정하고 있는지 한참 찾았거든요..
삭제한다는 생각은 못하고 여기서 수정을 해줬는데 앞으로는 삭제하고 구성해나가야겠어요!!


return (
<S.PageContainer>
<Header time={time} activeButton={activeButton} setActiveButton={setActiveButton} />

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.

스타일을 이렇게 따로 파일을 떼서 만들어주는것도 좋네요..!!👍👍

Choose a reason for hiding this comment

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

App.css 같은 기본 설정 파일을 제거하고나서도, 각 요소에서 따로 스타일을 지정해주기보다 전체적인 기본 틀은 global로 설정해주는게 좋나요???

</tbody>
</S.Table>
) : (
<S.NoDataMessage>저장된 게임 정보가 없어요🥲</S.NoDataMessage>

Choose a reason for hiding this comment

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

저장된 기록이 없을때 따로 처리를 해주신것도 좋네욤

const newIntervalId = setInterval(() => setTime(prev => prev + 0.01), 10);
setIntervalId(newIntervalId);
startTimer();
}

Choose a reason for hiding this comment

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

setIntervalId로 타이머의 ID값을 저장해서 사용하는 방식이 생각지 못한 부분이었어요!!
한가지 궁금한 점은
pages/games/game.jsx에서 타이머 기능을 정의할 때 setInterval(() => setTime(prev => prev + 0.01), 10)을 해주는 부분이 있는데, 여기서도 setInterval()을 반복해서 써줘야하나요???

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
❤️최민 최민 🌳과제 과제입니다 🐻Design 디자인 추가입니다 💫Feature 새로운 기능 구현입니다
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature] - 3주차 실습, 과제
3 participants