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
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions week3/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
8 changes: 8 additions & 0 deletions week3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# React + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
14 changes: 14 additions & 0 deletions week3/index.html
Original file line number Diff line number Diff line change
@@ -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로 바꾸기~

<head>
<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.

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

</head>
<body>
<div id="root"></div>
<div id="modal-root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
32 changes: 32 additions & 0 deletions week3/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "week3",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.27.0",
"styled-components": "^6.1.13"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
"eslint": "^9.13.0",
"eslint-plugin-react": "^7.37.1",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.13",
"globals": "^15.11.0",
"vite": "^5.4.9"
}
}
Binary file added week3/public/fonts/NanumSquareRoundB.ttf
Binary file not shown.
Binary file added week3/public/fonts/NanumSquareRoundL.ttf
Binary file not shown.
Binary file added week3/public/fonts/NanumSquareRoundR.ttf
Binary file not shown.
1 change: 1 addition & 0 deletions week3/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions week3/src/App.css
Original file line number Diff line number Diff line change
@@ -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를 어디서 설정하고 있는지 한참 찾았거든요..
삭제한다는 생각은 못하고 여기서 수정을 해줬는데 앞으로는 삭제하고 구성해나가야겠어요!!

max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}

.card {
padding: 2em;
}

.read-the-docs {
color: #888;
}
18 changes: 18 additions & 0 deletions week3/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// import { useState } from 'react'
import './App.css'
import ProfileCard from './component/ProfileCard'
import Game from './pages/games/game'
import GlobalStyle from './styles/global';

function App() {

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

Choose a reason for hiding this comment

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

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

<Game />
</>
)
}

export default App
1 change: 1 addition & 0 deletions week3/src/assets/react.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions week3/src/component/ProfileCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useState } from 'react'
import { members } from "../data/member";
import styled from "@emotion/styled"

// 여기 컴포넌트에는 카드 형식만 만들고 App.jsx나 페이지에서 정보 넣고 돌리기

const ProfileCard = ({ }) => {

return (
<MainContainer>
{members.map((k)=>{
const [count, setCount] = useState(0)

return(
<Container>
<div key={k.id}>
<MemberName>{k.name}</MemberName>
<MemberInfo>{k.englishName}</MemberInfo>
<MemberInfo>{k.github}</MemberInfo>
<Btn>
<div>{count}</div>
<button onClick={() => setCount((count) => count + 1)}>
Like
</button>
</Btn>
</div>
</Container>
)

})}
</MainContainer>
)
}

export default ProfileCard;



const MainContainer= styled.div`
display : flex ;
justify-content: center;
flex-wrap: wrap;
gap:1rem;
`;

const Container = styled.div`
width:10rem;
height:10rem;
background-color: #b0f0c6;
border-radius: 1rem;
;`

const MemberName = styled.div`
font-size:1.5rem;
padding-top: 1rem;
;`

const MemberInfo = styled.div`
font-size:1rem;
;`

const Btn = styled.div`
display: flex;
justify-content: center;
align-items: center;
gap:1rem;
margin-top:0.5rem;
`;
114 changes: 114 additions & 0 deletions week3/src/component/games/gameBoard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { useState, useEffect } from 'react';
import * as S from './styled';
import Modal from './modal';

function GameBoard({ startTimer, stopTimer, resetTimer }) {
const [cards, setCards] = useState([]);
const [clickedNumbers, setClickedNumbers] = useState(new Set());
const [currentClick, setCurrentClick] = useState(1);
const [usedNumbers, setUsedNumbers] = useState(new Set());
const [isGameOver, setIsGameOver] = useState(false);
const [time, setTime] = useState(0);
const [intervalId, setIntervalId] = useState(null);
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의 개수와 관련된 부분을 모두 이런 방식으로 수정해주면 될 것 같습니다!

const mixNumbers = initialNumbers.sort(() => Math.random() - 0.5);

const threeGrid = [];
for (let i = 0; i < 9; i += 3) {
threeGrid.push(mixNumbers.slice(i, i + 3));
}

setCards(threeGrid);
};

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

Choose a reason for hiding this comment

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

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


setUsedNumbers((prev) => new Set(prev).add(randomAfterNumber));
return randomAfterNumber;
};

const handleCardClick = (num, rowIndex, colIndex) => {
if (num === currentClick) {
if (currentClick === 1) {
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()을 반복해서 써줘야하나요???


setClickedNumbers((prev) => new Set(prev).add(num));

if (num >= 10) {
const updatedCards = cards.map((row, rIdx) =>
row.map((card, cIdx) => (rIdx === rowIndex && cIdx === colIndex ? '' : card))
);
setCards(updatedCards);
} else {
const randomAfterNumber = createNewCard();
const updatedCards = cards.map((row, rIdx) =>
row.map((card, cIdx) => (rIdx === rowIndex && cIdx === colIndex ? randomAfterNumber : card))
);
setCards(updatedCards);
}

setCurrentClick(prev => prev + 1);

if (currentClick === 18) {
clearInterval(intervalId);
stopTimer();
setIsGameOver(true);
}
} else {
alert("틀렸어요🥲\n" + currentClick + "를 클릭하세요 !");

}
};

const closeModal = () => {
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));
Comment on lines +74 to +81

Choose a reason for hiding this comment

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

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


setIsGameOver(false);
setTime(0);
setCurrentClick(1);
setClickedNumbers(new Set());
setUsedNumbers(new Set());
resetTimer();
startCards();
};

useEffect(() => {
startCards();
}, []);

return (
<S.NextNumber>다음 숫자 : {currentClick}
<S.Board>
{cards.map((row, rowIndex) => (
<S.Row key={rowIndex}>
{row.map((num, colIndex) => (
<S.Col key={colIndex} onClick={() => handleCardClick(num, rowIndex, colIndex)}>
{num}
</S.Col>
))}
</S.Row>
))}
<Modal isOpen={isGameOver} onClose={closeModal} time={time} />
</S.Board>
</S.NextNumber>
);
}

export default GameBoard;
45 changes: 45 additions & 0 deletions week3/src/component/games/header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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으로 했는지 알 수 있을까요??

const [showHeaderRight, setShowHeaderRight] = useState(true);

const handleButtonClick = (button) => {
setActiveButton(button);
setShowHeaderRight(button === "game");
};

return (
<S.Header>
<S.HeaderLeft>
<S.HeaderMent>1 to 50</S.HeaderMent>
<S.ButtonGame
onClick={() => handleButtonClick("game")}
isActive={activeButton === "game"}
>
게임
</S.ButtonGame>
<S.ButtonRanking
onClick={() => handleButtonClick("ranking")}
isActive={activeButton === "ranking"}
>
랭킹
</S.ButtonRanking>
</S.HeaderLeft>
{showHeaderRight && (
<S.HeaderRight>
<S.Dropdown>
<select>
<option value="1">Level 1</option>
<option value="2">Level 2</option>
<option value="3">Level 3</option>
</select>
</S.Dropdown>
<S.Timer>{time}초</S.Timer>
</S.HeaderRight>
)}
</S.Header>
);
}

export default Header;
Loading