-
Notifications
You must be signed in to change notification settings - Fork 0
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주차 과제] 🌼 쵸키 푸키를 찾아라 💖 #8
base: main
Are you sure you want to change the base?
Changes from all commits
e07952f
3018ee5
15ed4e3
32bcda2
5778688
ffc9b24
98e9381
91aefe4
7db65d4
311e61f
10bf217
b1961c3
1affc68
24770bb
5ffbda0
a4f866b
eb59ead
056989e
b38baa2
450054d
c9bbefa
df4a1ba
1f987eb
c95df59
8679a34
0c4064d
66ec068
481bca6
2509faa
7588080
e7caef2
96ac8ca
6644764
87c945a
27991e3
364e05f
621d0ed
000920d
4556399
314e787
5442735
5694a0b
e474728
292a49a
bb8c04c
ffd5dfc
a3f469a
5cd57a2
5452b9b
c0a3c5b
aa3abe6
fa9c773
a7627e8
f1a28d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
module.exports = { | ||
env: { browser: true, es2020: true }, | ||
extends: [ | ||
'eslint:recommended', | ||
'plugin:react/recommended', | ||
'plugin:react/jsx-runtime', | ||
'plugin:react-hooks/recommended', | ||
], | ||
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, | ||
settings: { react: { version: '18.2' } }, | ||
plugins: ['react-refresh'], | ||
rules: { | ||
'react-refresh/only-export-components': 'warn', | ||
}, | ||
} |
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? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link | ||
rel="icon" | ||
type="image/svg+xml" | ||
href="https://item.kakaocdn.net/do/cc744df91dd6ecf377d4584e79dd0e31b3a18fdf58bc66ec3f4b6084b7d0b570" | ||
/> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>쵸키푸키를 찾아랏🐾</title> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<div id="modal"></div> | ||
<script type="module" src="/src/main.jsx"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{ | ||
"name": "week3", | ||
"private": true, | ||
"version": "0.0.0", | ||
"type": "module", | ||
"scripts": { | ||
"dev": "vite", | ||
"build": "vite build", | ||
"lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0", | ||
"preview": "vite preview" | ||
}, | ||
"dependencies": { | ||
"prop-types": "^15.8.1", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"style-components": "^0.1.0" | ||
}, | ||
"devDependencies": { | ||
"@types/react": "^18.0.28", | ||
"@types/react-dom": "^18.0.11", | ||
"@vitejs/plugin-react": "^4.0.0", | ||
"eslint": "^8.38.0", | ||
"eslint-plugin-react": "^7.32.2", | ||
"eslint-plugin-react-hooks": "^4.6.0", | ||
"eslint-plugin-react-refresh": "^0.3.4", | ||
"vite": "^4.3.2" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { ThemeProvider } from "styled-components"; | ||
import theme from "./styles/theme"; | ||
import { GlobalStyle } from "./styles/GlobalStyle"; | ||
import Header from "./components/Header"; | ||
import Button from "./components/Button"; | ||
|
||
function App() { | ||
return ( | ||
<ThemeProvider theme={theme}> | ||
<GlobalStyle /> | ||
<Header /> | ||
<Button /> | ||
</ThemeProvider> | ||
); | ||
} | ||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { useEffect, useState } from "react"; | ||
import styled from "styled-components"; | ||
import { | ||
easyCardList, | ||
normalCardList, | ||
hardCardList, | ||
} from "../constants/cardImgList"; | ||
import CommonPage from "./CommonPage"; | ||
|
||
const Button = () => { | ||
const levelData = ["EASY", "NORMAL", "HARD"]; | ||
// 기본 난이도 'EASY'로 설정 | ||
const [level, setLevel] = useState(0); | ||
const [cardList, setCardList] = useState(easyCardList); | ||
const [shuffle, setShuffle] = useState(0); | ||
|
||
useEffect(() => { | ||
switch (level) { | ||
case 0: | ||
setCardList(easyCardList); | ||
break; | ||
|
||
case 1: | ||
setCardList(normalCardList); | ||
break; | ||
|
||
case 2: | ||
setCardList(hardCardList); | ||
break; | ||
|
||
default: | ||
break; | ||
} | ||
}, [level, shuffle]); | ||
|
||
return ( | ||
<> | ||
<ResetButton | ||
type="button" | ||
onClick={() => { | ||
setShuffle((prev) => prev + 1); | ||
}} | ||
> | ||
RESET | ||
</ResetButton> | ||
|
||
<LevelBtnContainer> | ||
{levelData.map((data, idx) => { | ||
return ( | ||
<LevelButton | ||
key={idx} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 키값 잊지않구 준거 칭찬해~! |
||
type="button" | ||
isClick={idx === level} | ||
onClick={() => { | ||
setLevel(idx); | ||
}} | ||
> | ||
{data} | ||
</LevelButton> | ||
); | ||
})} | ||
</LevelBtnContainer> | ||
|
||
{<CommonPage cardList={cardList} />} | ||
</> | ||
); | ||
}; | ||
|
||
export default Button; | ||
|
||
const ResetButton = styled.button` | ||
display: flex; | ||
justify-content: center; | ||
position: absolute; | ||
z-index: 1; | ||
top: 2rem; | ||
right: 0; | ||
width: 7rem; | ||
margin: 1rem; | ||
padding: 1.5rem 1rem; | ||
border: 0; | ||
border-radius: 1rem; | ||
box-shadow: 0.3rem 0.3rem 0.3rem ${({ theme }) => theme.colors.lightGreen}; | ||
background-color: ${({ theme }) => theme.colors.darkGreen}; | ||
color: ${({ theme }) => theme.colors.white}; | ||
|
||
&:hover { | ||
background-color: ${({ theme }) => theme.colors.white}; | ||
color: ${({ theme }) => theme.colors.darkGreen}; | ||
} | ||
|
||
font-family: ${({ theme }) => theme.font.buttonFont}; | ||
font-size: 1.5rem; | ||
`; | ||
|
||
const LevelBtnContainer = styled.div` | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
position: absolute; | ||
top: 13rem; | ||
left: 0; | ||
right: 0; | ||
`; | ||
|
||
const LevelButton = styled.button` | ||
margin: 0 1rem; | ||
padding: 1rem 1.5rem; | ||
box-shadow: 0.3rem 0.3rem 0.3rem ${({ theme }) => theme.colors.purple}; | ||
border: 0; | ||
border-radius: 1rem; | ||
|
||
background-color: ${({ theme, isClick }) => | ||
isClick ? theme.colors.purple : theme.colors.lightPink}; | ||
|
||
color: ${({ theme, isClick }) => | ||
isClick ? theme.colors.lightPink : theme.colors.purple}; | ||
|
||
font-family: ${({ theme }) => theme.font.buttonFont}; | ||
font-size: 1.3rem; | ||
`; |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,105 @@ | ||||||||||
import styled from "styled-components"; | ||||||||||
import { useEffect, useState, useMemo } from "react"; | ||||||||||
import SingleCard from "./SingleCard"; | ||||||||||
import Header from "./Header"; | ||||||||||
import Modal from "./Modal"; | ||||||||||
import ModalPortal from "./ModalPortal"; | ||||||||||
|
||||||||||
const CommonPage = (cardList) => { | ||||||||||
const [choiceOne, setChoiceOne] = useState(null); | ||||||||||
const [choiceTwo, setChoiceTwo] = useState(null); | ||||||||||
Comment on lines
+9
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 각각 state로 만들지 않고 요런식으로 배열로 관리하는건 오때?!
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 옹 참고해보께! |
||||||||||
const [counter, setCounter] = useState(0); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 점수를 나타내는거면 좀 더 직관적으로 score라고 변수명 지어도 좋을거같아!! ㅎㅎ |
||||||||||
const [modalOn, setModalOn] = useState(false); | ||||||||||
const [disabled, setDisabled] = useState(false); | ||||||||||
|
||||||||||
const flippedCard = document.getElementsByClassName("flipped"); | ||||||||||
|
||||||||||
// useMemo() 활용하여 cardList가 변경되지 않을 경우, 이전 값을 재사용하도록 구현 | ||||||||||
const copiedCardList = useMemo(() => { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 useMemo 사용햇구나 싱기...! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 웅 useMemo에 dependency로는 cardList를 줘서 리스트가 변하지 않을 경우에는 카드 배열이 변하지 않도록 했어..!! 안그러면 카드를 하나 클릭할 때마다 계속 카드 배열이 변하더라구 흑흑 |
||||||||||
setCounter(0); | ||||||||||
// JSON.parse(JSON.stringify(obj)): 깊은 복사 | ||||||||||
const copied = JSON.parse( | ||||||||||
JSON.stringify( | ||||||||||
// Object.keys(): 객체를 문자열 배열로 변환 | ||||||||||
Object.keys(cardList) | ||||||||||
.map((item) => cardList[item]) | ||||||||||
// flat(): 하나의 배열로 만들고자 사용 | ||||||||||
.flat() | ||||||||||
) | ||||||||||
); | ||||||||||
// 카드 선택을 하다가 중간에 reset 버튼이나 레벨 선택을 다시 하면 이전에 저장되어 있던 카드 선택 정보 모두 삭제 | ||||||||||
copied.map((it) => (it.matched = false)); | ||||||||||
Comment on lines
+20
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 복사한 두 카드가 원본 객체를 참조하기 때문에 |
||||||||||
|
||||||||||
return copied.sort(() => Math.random() - 0.5); | ||||||||||
}, [cardList]); | ||||||||||
|
||||||||||
// 값이 null이 아니면 이미 해당 값은 선택되어 있다는 것 | ||||||||||
const handleChoice = (card) => { | ||||||||||
choiceOne ? setChoiceTwo(card) : setChoiceOne(card); | ||||||||||
}; | ||||||||||
|
||||||||||
useEffect(() => { | ||||||||||
if (choiceOne && choiceTwo) { | ||||||||||
// 두 개의 카드가 같은지 확인하는 동안 다른 카드 선택 불가 | ||||||||||
setDisabled(true); | ||||||||||
|
||||||||||
if (choiceOne.name === choiceTwo.name) { | ||||||||||
setChoiceOne((choiceOne.matched = true)); | ||||||||||
setChoiceTwo((choiceTwo.matched = true)); | ||||||||||
setCounter((prev) => prev + 1); | ||||||||||
resetTurn(); | ||||||||||
|
||||||||||
flippedCard.length === copiedCardList.length | ||||||||||
? setModalOn(true) | ||||||||||
: setModalOn(false); | ||||||||||
Comment on lines
+52
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
요렇게 하면 좀 더 간결하게 쓸 수 있겠당!ㅎㅎ |
||||||||||
} else { | ||||||||||
setTimeout(() => resetTurn(), 1000); | ||||||||||
} | ||||||||||
} | ||||||||||
}, [choiceOne, choiceTwo, flippedCard, copiedCardList]); | ||||||||||
|
||||||||||
// 두 개의 카드가 선택된 후, 각 카드 정보 초기화 | ||||||||||
const resetTurn = () => { | ||||||||||
setChoiceOne(null); | ||||||||||
setChoiceTwo(null); | ||||||||||
|
||||||||||
// 검사가 끝나면 다른 카드 선택 가능 | ||||||||||
setDisabled(false); | ||||||||||
}; | ||||||||||
|
||||||||||
return ( | ||||||||||
<> | ||||||||||
<Header counter={counter} length={copiedCardList.length / 2} /> | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요기 현지가 위에 counter score로 바꾸는거 말한거처럼 length 도 �totalScore 같은걸로 바꿔주면 더 직관적일거 같아!! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 죠아! |
||||||||||
<CardContainer> | ||||||||||
{copiedCardList.map((data, idx) => { | ||||||||||
return ( | ||||||||||
<SingleCard | ||||||||||
key={idx} | ||||||||||
data={data} | ||||||||||
handleChoice={handleChoice} | ||||||||||
flipped={data === choiceOne || data === choiceTwo || data.matched} | ||||||||||
disabled={disabled} | ||||||||||
/> | ||||||||||
); | ||||||||||
})} | ||||||||||
</CardContainer> | ||||||||||
|
||||||||||
{modalOn && ( | ||||||||||
<ModalPortal> | ||||||||||
<Modal onClose={() => setModalOn(false)} /> | ||||||||||
</ModalPortal> | ||||||||||
)} | ||||||||||
</> | ||||||||||
); | ||||||||||
}; | ||||||||||
|
||||||||||
export default CommonPage; | ||||||||||
|
||||||||||
const CardContainer = styled.div` | ||||||||||
display: flex; | ||||||||||
flex-wrap: wrap; | ||||||||||
justify-content: center; | ||||||||||
align-items: center; | ||||||||||
|
||||||||||
margin: 20rem 7rem 0rem; | ||||||||||
`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
왜 리셋버튼을 누르면 setShuffle에 +1을 하는거야??!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
나두 궁금!!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아! 이거 reset 버튼이 클릭될 때마다 위에 페이지들을 다시 렌더링시켜서 카드 배열을 변하게 해주려고 shuffle을 생성했어! 그래서 버튼이 클릭될 때마다 shuffle 값에 변화를 줘서 useEffect가 실행될 수 있게 구현해봤어..!!