-
Notifications
You must be signed in to change notification settings - Fork 1
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주차 기본/심화/공유 과제 ] 재미있는 카드게임 #3
base: main
Are you sure you want to change the base?
Conversation
카드 클릭 -> 오픈 매칭 성공 -> 오픈 고정 매칭 실패 -> 오픈 취소
카드 매칭 -> 스코어 +1
headercontroller -> header 로 짧게 변경
### 🧩 기본 과제 | ||
|
||
1. **전체적인 game flow** | ||
|
||
- [x] 카드 두개를 오픈해서 같은 카드라면 카드를 오픈 상태에 두고 score를 올립니다. | ||
- [x] score를 모두 채웠을 때 게임이 종료되고 축하 모달이 뜹니다. | ||
- [x] 최초 카드 데이터는 데이터 상수 파일로 생성해서 사용합니다. | ||
|
||
2. **header 구현** | ||
- [x] 페이지의 이름과 score 현황을 구현합니다. | ||
- [x] 카드의 짝을 맞출 때 마다 score을 상승시킵니다. | ||
- [x] 최초데이터 데이터 상수 파일 생성 | ||
3. **card section 구현** | ||
|
||
- [x] 카드가 선택되었을 때 카드를 open합니다. | ||
- [x] 두 번째 카드가 클릭될 때 까지 카드는 open상태여야합니다. | ||
|
||
- 두 번째 카드를 클릭하였을 때 | ||
- [x] 정답일 경우 카드 두개를 open 상태로 고정하고 score를 올립니다. | ||
- [x] 오답일 경우 카드 두개를 다시 close합니다. | ||
|
||
4. **게임에 성공하였을 때** | ||
|
||
- [x] 축하 모달이 뜨도록 합니다. | ||
- [x] 모달을 닫았을 때 카드를 모두 close하고 셔플합니다. |
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.
저도 처음 보네요..... 이게 파워 J인가...?
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.
우앙... 저는 맨날 노션 들어가서 하나씩 확인했는데 이렇게 해두면 구현할 때도 편하고 PR 날릴 때도 리드미만 복사해오면 되겠네요!! 짱이에요,,,,,,,
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.
자꾸 제가 뭘했는지 뭐 해야하는지 까먹어서 기능 구현하면 하나씩 체크 표시하게 되었습니당...ㅎ
<BrowserRouter> | ||
<GlobalWrapper> | ||
<Routes> | ||
<Route path="/" element={<GamePage />} /> | ||
</Routes> | ||
</GlobalWrapper> | ||
</BrowserRouter> | ||
</> |
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.
리액트 라우터가 업데이트 되면서,
공식문서에서 createBrowserRouter 사용을 더 권하는 것 같아요!
링크 남겨둘테니 저희 합세에선 이렇게 적용해봅시다 ㅎㅎ!
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.
옹 하나 배워갑니다!!! @ExceptAnyone
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.
오 처음 보는건데 신기하네요! 합세때 적용할 수 있도록 수정했습니다
useEffect(() => { | ||
if (score === level) { | ||
setModalOpen(true); | ||
} | ||
}, [score]); |
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.
저도 이번에 주용님께 배웠는데, 요기 useEffect는 없어도 될거에요!
한 번 지우고 if문만 남겨서 해보시는 방법도 좋을 것 같습니당
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.
혹시 왜 그런것일까요..? 저는 useEffect를 지우니까 안돌아가서요...
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.
아마 무한 리렌더링 발생 방지 경고가 뜰 수도 있어요!
저희 금잔디 조원인 이진님도 같은 오류가 발생했었는데요,
if문 안에 조건에 상태 조건을 하나 추가해줘야해요!
한 번 어떤 조건이 들어가야하는지 고민해보시길 ㅎㅎ
안되면 바로 콜해주세요!
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.
이 로직을 그대로 useEffect없이 updateScore 로직에 넣으면 적용이될 것 같은데요? 한번 시도해보세요~~
const updateScore = (newScore) => {
setScore(newScore);
if (newScore === level) {
setModalOpen(true);
}
};
@@ -0,0 +1,13 @@ | |||
import React from 'react'; | |||
import * as H from './HeaderStyle'; |
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.
헤더는 H라서 H dot naming을 하신걸까요??
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.
넵 맞습니다! 여러개 import해올 때 헷갈려서 저렇게 쓰게 되었는데 호옥시 다른 방법이 있는걸까요??
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.
아 아니요! 단순히 궁금했습니다 ㅎㅎ!
|
||
export default function GamePage() { | ||
const [score, setScore] = useState(0); | ||
const [level, setLevel] = useState(5); |
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.
조금 디테일적인 부분이긴한데, 지금 저희는 같은 과제 속에 같은 기능을 구현하고 있기에
이 숫자 5가 뭘 의미하는지 잘 알고있죠 ㅎㅎ
그러나 추후 합세와 웹잼에서는 서로 다른 기능을 개발하고 있기에 코드리뷰를 받을 때
다른 사람입장에서 이 숫자5가 무엇을 의미하는지 모를 수도 있어요!
그래서 저는 이런 부분을 상수로 빼두고 의미있는 네이밍을 하려고 노력하는데 한 번 고민해보셔도 좋을 것 같아요 ㅎㅎ!!
export const GAME_LEVEL = Object.freeze({
EASY: 5,
NORMAL:7,
HARD:9
})
이런 방법이 있겠군요 ㅎㅎ
제 이번 카드 뒤집기 게임 과제 코드 중 constant 속을 한 번 참고해보셔도 좋을 것 같습니다!
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.
조금 디테일적인 부분이긴한데, 지금 저희는 같은 과제 속에 같은 기능을 구현하고 있기에 이 숫자 5가 뭘 의미하는지 잘 알고있죠 ㅎㅎ
그러나 추후 합세와 웹잼에서는 서로 다른 기능을 개발하고 있기에 코드리뷰를 받을 때 다른 사람입장에서 이 숫자5가 무엇을 의미하는지 모를 수도 있어요!
그래서 저는 이런 부분을 상수로 빼두고 의미있는 네이밍을 하려고 노력하는데 한 번 고민해보셔도 좋을 것 같아요 ㅎㅎ!!
export const GAME_LEVEL = Object.freeze({ EASY: 5, NORMAL:7, HARD:9 })이런 방법이 있겠군요 ㅎㅎ
제 이번 카드 뒤집기 게임 과제 코드 중 constant 속을 한 번 참고해보셔도 좋을 것 같습니다!
좋네요...!! 저도 놓쳤던 부분인데 리팩때 꼭 적용해봐야겠네요 👍🏻👍🏻
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.
오오 섬세하게 봐주시고 코드까지 보여주시다니! 이해가 잘되었습니다! 감사합니다 :)
const levelSettings = [ | ||
{ label: 'Easy', level: 5 }, | ||
{ label: 'Normal', level: 7 }, | ||
{ label: 'Hard', level: 9 }, | ||
]; | ||
return ( | ||
<BtnWrapper> | ||
{levelSettings.map((setting) => ( | ||
<LevelBtn |
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.
여기도 constant폴더 속 상수로 분리해도 좋고,
아니면 상태에 따라 변하는 부분이 아니니깐 컴포넌트 밖에서 선언해도 괜찮아요!
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.
넵 감사합니다! 반영했습니다!
export default function Card(props) { | ||
return ( | ||
<C.CardWrapper $isOpen={props.open}> | ||
<C.FrontCard $img={props.img}></C.FrontCard> | ||
<C.BackCard></C.BackCard> |
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.
넵!! 충고 감사합니다! 수정했습니다!
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.
잘한다는 얘기 많이 들었는데 역시... 전체적으로 JS의 내장 함수들을 잘 사용한다는 느낌을 많이 받았어요!! 특히 기억에 남는 건 reducer 메소드인데, 이건 저도 잘 몰랐던 부분이라 덕분에 좋은 메소드 하나 공부하고 알아가네요 ㅎㅎㅎ
질문 남겨주신 것에 대해 답변을 해볼게요!!
1. 렌더링
처음에 2번씩 찍히는 이유는 바로 StrictMode 때문입니다. StrictMode는 개발 환경에서만 활성화되는 기능으로, 컴포넌트의 예상치 못한 부작용을 감지하기 위해 렌더링을 두 번 수행하는 모드입니다!! 이게 적용되어 있는 부분은 main.jsx에서 확인할 수 있는데, React.StrictMode로 감싼 부분이 해당 부분입니다!! 제거하려면 감싸는 태그를 제거하면 끝입니다! 실제로 이것을 제거하고 채현이가 캡쳐한 상황을 재연해보면 다음과 같이 1번씩만 찍히는 것을 볼 수 있습니다!!
초기 렌더 시 useEffect가 동작해서 useEffect 내부 함수들을 모두 실행하므로 콘솔에 한 번씩 찍히고, state가 변경되니 리렌더링이 1번 일어나서 "렌더링"이 콘솔에 추가적으로 한 번 더 찍히는 정상적인 상황입니다!!
여기서 연속으로 4개의 state를 바꿨으니 리렌더링이 4번 일어나야 하는데 왜 렌더링이 1번 찍히지?라고 궁금하실 수도 있어서 오지랖을 좀 부려보자면.... React는 state를 업데이트 할 때 성능 및 리렌더링 최적화를 위해 batch 기법이라는 것을 사용해요!! batch 기법은 여러 개의 상태 업데이트를 그룹화해서 한 번의 리렌더링으로 처리해버리는 기법이에요!! 그래서 4개의 상태 업데이트가 있었지만, 리렌더링은 1번 발생한 것입니다 ㅎㅎㅎ
Strict(안전) 모드라고는 하지만, 제거한다고 성능상 문제가 발생하지는 않으니, 지우고 개발 진행하셔도 됩니다!!
2. id 비교
제가 잘 이해한 게 맞다면, id 뒤에 level만큼 더해서 level을 나눈 나머지로 id를 세팅하면
const cardPairs = selectedCards.reduce(
(acc, card) => [
...acc,
{ ...card, id: card.id + level }, // ID 충돌 방지
{ ...card, id: card.id + level * 2 }, // ID 충돌 방지
],
[],
);
이런 식으로 작성했다고 이해를 했는데요!!
만약 level이 5라면, card.id가 0인 경우와 card.id가 5인 경우가 (card.id + level) % level 값이 같으므로 제대로 동작하지 않을 가능성이 있습니다!!
1000처럼 큰 수가 아니더라도, 더하는 수를 카드들의 id 중 최댓값 * 2보다 크게 해준다면, 비교가 꼬이지 않을 것 같네요!!
정안님이 남겨주신 리뷰들도 정말 유익하고 좋은 리뷰들이니 꼭 읽어보고 적용해봤으면 좋겠어요!!!
@ExceptAnyone 최고의 OB... 👍🏻👍🏻👍🏻
이번 과제 난이도가 높았는데 심화까지 완성하시느라 고생했어요!!!!! 👍🏻👍🏻
<BrowserRouter> | ||
<GlobalWrapper> | ||
<Routes> | ||
<Route path="/" element={<GamePage />} /> | ||
</Routes> | ||
</GlobalWrapper> | ||
</BrowserRouter> | ||
</> |
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.
옹 하나 배워갑니다!!! @ExceptAnyone
const GlobalWrapper = styled.main` | ||
margin: 0 auto; | ||
`; |
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.
이 요소가 Routes 상위에 있는 걸로 보아 전역 스타일 역할을 하는 것 같은데 GlobalStyle로 빼주는 건 어떨까요~?
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.
아! 이해했습니다! 수정하겠습니다~~
|
||
// 랜덤 카드 뽑기 | ||
const getRandomElement = (arr) => { | ||
console.log('getRandomElement'); |
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.
console문 지워주기!!!
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.
이걸 놓쳤네요......ㅎ 감사합니다
let result = new Set(); | ||
while (result.size < level) { | ||
const randomElement = arr[Math.floor(Math.random() * arr.length)]; | ||
result.add(randomElement); | ||
} | ||
return Array.from(result); |
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.
중복제거로 Set 사용한 거 신박하네요!!!!!
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.
구글링하다가 좋아 보여서 하게 되었습니당....ㅎ
}, [level, shuffle]); | ||
|
||
function chooseCard(e, id) { | ||
e.stopPropagation(); |
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.
혹시 이렇게 이벤트 전파를 방지시킨 특별한 이유가 있을까요??
코드 구조를 보면 CardHandler 상위 요소에 특별히 click 이벤트 핸들러가 바인딩되어있지 않은 것 같아서 이 부분이 없어도 클릭 이벤트가 의도대로 잘 동작할 것 같다는 생각이 듭니다!!
만약 이유가 있다고 하더라고, e.stopPropagation()으로 강제로 이벤트 전파를 막는 방식은 지양해야 합니다!! 무엇이든지 앞에 "강제"가 붙는 행위는 사이드 이펙트가 발생할 가능성을 내포하고 있기 때문이에요!!
만약 나중에 이벤트 전파를 막아야 하는 상황이 온다면, 그것은 코드 구조를 잘못 짰다는 방증이니 최대한 코드 구조를 수정해서 이벤트 전파를 막는 방향으로 수정하는 것이 맞습니다!!!
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.
헉....저를 정확히 알고 계십니다.....
클릭 이벤트가 자꾸 다른 카드로 영향이 가서 검색하다가 저 코드를 사용하라고 나와서 적용했는데
그냥 저의 코드 문제였습니다...ㅎ
로직 변경을 해서 오류를 고쳤는데 저 코드를 아무생각없이 남겨 두었습니다....
코드 리뷰 감사합니다! 좋은 정보를 얻어갑니다!
height: 6rem; | ||
background-color: ${(props) => props.theme.colors.grey}; | ||
border: none; | ||
border-radius: 20px; |
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.
단위 px인 거 확인하기!!
export const ModalContainer = styled.div` | ||
position: fixed; | ||
top: 0; | ||
left: 0; | ||
right: 0; | ||
bottom: 0; | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
background-color: rgba(0, 0, 0, 0.5); | ||
`; | ||
|
||
export const ModalWrapper = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
justify-content: center; | ||
width: 50rem; | ||
height: 10rem; | ||
background-color: ${(props) => props.theme.colors.white}; | ||
border-radius: 10px; | ||
font-size: 3rem; | ||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | ||
`; | ||
|
||
export const ConfirmBtn = styled.button` | ||
padding: 10px 20px; | ||
background-color: ${(props) => props.theme.colors.pink}; | ||
color: white; | ||
border: none; | ||
border-radius: 5px; | ||
cursor: pointer; | ||
margin-top: 20px; | ||
`; |
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.
CSS를 작성할 때 같은 속성을 가진 CSS끼리 묶어주고, 속성별로 개행을 해주면 가독성이 훨씬 좋아져요!! 저는 Mozilla의 속성 정렬 순서에 맞춰 정렬하고 있어요!! 참고해보시면 좋을 것 같아요!!
https://milooy.github.io/TIL/CSS/css-property-order.html#intro
또 이 파일에서 전체적으로 px단위로 설정되어 있는데 단위 한 번 확인 부탁드립니다아~!
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.
오 급하게 한다고 코드가 정말 개판이었네요.....부끄럽습니다ㅎㅎㅎㅎ 감사합니다 :)
|
||
export default function GamePage() { | ||
const [score, setScore] = useState(0); | ||
const [level, setLevel] = useState(5); |
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.
조금 디테일적인 부분이긴한데, 지금 저희는 같은 과제 속에 같은 기능을 구현하고 있기에 이 숫자 5가 뭘 의미하는지 잘 알고있죠 ㅎㅎ
그러나 추후 합세와 웹잼에서는 서로 다른 기능을 개발하고 있기에 코드리뷰를 받을 때 다른 사람입장에서 이 숫자5가 무엇을 의미하는지 모를 수도 있어요!
그래서 저는 이런 부분을 상수로 빼두고 의미있는 네이밍을 하려고 노력하는데 한 번 고민해보셔도 좋을 것 같아요 ㅎㅎ!!
export const GAME_LEVEL = Object.freeze({ EASY: 5, NORMAL:7, HARD:9 })이런 방법이 있겠군요 ㅎㅎ
제 이번 카드 뒤집기 게임 과제 코드 중 constant 속을 한 번 참고해보셔도 좋을 것 같습니다!
좋네요...!! 저도 놓쳤던 부분인데 리팩때 꼭 적용해봐야겠네요 👍🏻👍🏻
return ( | ||
<CardContainer> | ||
{cards.map((card, index) => ( | ||
<CardWrapper key={index} onClick={(e) => chooseCard(e, card.id)}> |
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.
key 값으로 map의 순회 index를 주는 것은 지양해야 합니다!!
idx가 변경되는 이벤트가 발생하면 렌더되는 컴포넌트에서 사용하는 state가 의도하지 않은 방식으로 바뀔 수도 있기 때문이에요!!
고유하게 사용할 마땅한 값이 없으면 다음과 같이 해주는 방법도 많이 사용합니다!!
<CardWrapper key={index} onClick={(e) => chooseCard(e, card.id)}> | |
<CardWrapper key={`card-${index}`} onClick={(e) => chooseCard(e, card.id)}> |
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.
오 이런 방법이 있다니 신세계입니다! 적용 완료했습니다~~ㅎㅎ
### 🧩 기본 과제 | ||
|
||
1. **전체적인 game flow** | ||
|
||
- [x] 카드 두개를 오픈해서 같은 카드라면 카드를 오픈 상태에 두고 score를 올립니다. | ||
- [x] score를 모두 채웠을 때 게임이 종료되고 축하 모달이 뜹니다. | ||
- [x] 최초 카드 데이터는 데이터 상수 파일로 생성해서 사용합니다. | ||
|
||
2. **header 구현** | ||
- [x] 페이지의 이름과 score 현황을 구현합니다. | ||
- [x] 카드의 짝을 맞출 때 마다 score을 상승시킵니다. | ||
- [x] 최초데이터 데이터 상수 파일 생성 | ||
3. **card section 구현** | ||
|
||
- [x] 카드가 선택되었을 때 카드를 open합니다. | ||
- [x] 두 번째 카드가 클릭될 때 까지 카드는 open상태여야합니다. | ||
|
||
- 두 번째 카드를 클릭하였을 때 | ||
- [x] 정답일 경우 카드 두개를 open 상태로 고정하고 score를 올립니다. | ||
- [x] 오답일 경우 카드 두개를 다시 close합니다. | ||
|
||
4. **게임에 성공하였을 때** | ||
|
||
- [x] 축하 모달이 뜨도록 합니다. | ||
- [x] 모달을 닫았을 때 카드를 모두 close하고 셔플합니다. |
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.
저도 처음 보네요..... 이게 파워 J인가...?
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.
style이랑 handler 분리한 거 보고 완전 감탄했습니다.... 🫣 사실 다들 하시는 건데 제가 아직 감자라서 몰랐던 거일 수도 있지만,,, 아무튼 정리 너무너무 잘해두셔서 감자인데도 불구하고 술술 읽히는 코드였던 것 같아요!! 너무 배워가기만 하고 알려드리는 게 없어 쪼금 민망한 코드리뷰지만,,, OB 분들이 꼼꼼하게 코드리뷰 남겨주신 것 같아 감탄만 하고 그것까지 배워가면서 코리 남기고 갑니다!! 수고하셨어요 최고 🩷✨
<BrowserRouter> | ||
<GlobalWrapper> | ||
<Routes> | ||
<Route path="/" element={<GamePage />} /> | ||
</Routes> | ||
</GlobalWrapper> | ||
</BrowserRouter> | ||
</> |
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.
우앙 Style 파일 별도로 정리한 거 너무 보기 깔끔하고 좋은 것 같아요...!! ✨ 한 번도 Style 파일을 별도로 정리한 적이 없어서 많은 역할을 하는 컴포넌트는 스타일까지 넣으면 너무 길어지곤 했는데,,, 좋은 방법 배워갑니다!! 🩷 동시에 별도로 정리할 경우 태그가 어떤 역할을 하는 친구인지 확인하려면 스타일을 적용하고자 하는 컴포넌트에서 확인해야 하니 태그 이름을 지을 때 더욱 신중해야겠다는 생각이 드네요... 🧐
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.
코드가 너무 길어져서 따로 임포트해와서 쓰게 되었습니당....
|
||
export default function LevelBtn({ children, isActive, onClick }) { | ||
return ( | ||
<B.Button isActive={isActive} onClick={onClick}> |
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.
isActive를 어디서 받아오는 건가 했는데 handler에서 받아오는 거군요.....!! 저는 하나의 컴포넌트에서 기능과 스타일까지 다 적용해서 길어지고 가독성도 낮아지기 일쑤였는데... handler를 굳이 분리해야 하나?? 싶다가도 채현님 코드 깔끔한 걸 보니 분리하는 게 좋겠다는 생각이 왕창 드네요... 🔥진짜 배워가는 게 너무너무 많은 코드입니다!!
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.
코드가 너무 길어지게 되어서 보기가 너무 힘들더라구요...그래서 그냥 따로 하는게 관리하기 편할 것 같아서 따로 빼주었습니다!
### 🧩 기본 과제 | ||
|
||
1. **전체적인 game flow** | ||
|
||
- [x] 카드 두개를 오픈해서 같은 카드라면 카드를 오픈 상태에 두고 score를 올립니다. | ||
- [x] score를 모두 채웠을 때 게임이 종료되고 축하 모달이 뜹니다. | ||
- [x] 최초 카드 데이터는 데이터 상수 파일로 생성해서 사용합니다. | ||
|
||
2. **header 구현** | ||
- [x] 페이지의 이름과 score 현황을 구현합니다. | ||
- [x] 카드의 짝을 맞출 때 마다 score을 상승시킵니다. | ||
- [x] 최초데이터 데이터 상수 파일 생성 | ||
3. **card section 구현** | ||
|
||
- [x] 카드가 선택되었을 때 카드를 open합니다. | ||
- [x] 두 번째 카드가 클릭될 때 까지 카드는 open상태여야합니다. | ||
|
||
- 두 번째 카드를 클릭하였을 때 | ||
- [x] 정답일 경우 카드 두개를 open 상태로 고정하고 score를 올립니다. | ||
- [x] 오답일 경우 카드 두개를 다시 close합니다. | ||
|
||
4. **게임에 성공하였을 때** | ||
|
||
- [x] 축하 모달이 뜨도록 합니다. | ||
- [x] 모달을 닫았을 때 카드를 모두 close하고 셔플합니다. |
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.
우앙... 저는 맨날 노션 들어가서 하나씩 확인했는데 이렇게 해두면 구현할 때도 편하고 PR 날릴 때도 리드미만 복사해오면 되겠네요!! 짱이에요,,,,,,,
null은 falsy한 값이므로 똑같게 사용됨 null인 경우와 다른 falsy한 값(0 등)일 경우가 다른 경우로 분기되어야 하는 상황은 제외
코드를 봤을때 이해하기 쉽도록 5로 하드코딩하는 것이 아닌 상수에서 값을 가져오기
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.
처음에 렌더링이 4번이라... 맛있는 useEffect 잔치가 벌어진 것 같아요 useEffect 실행되는 순서가 기억나시나요? side effect를 다루기에 score나 전체적인 state가 렌더링 된 후 다시 useEffect로 돌아가서 trigger가 성립하면 한 번 더 리렌더링을 시키기 때문에 이부분이 가장 유력하다고 볼 수 있습니다!
코리 이해 안가시면 오늘, 내일 카톡하거나 디코와주세요~
<C.CardWrapper $isOpen={open}> | ||
<C.FrontCard $img={img}></C.FrontCard> | ||
<C.BackCard></C.BackCard> | ||
</C.CardWrapper> |
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.
보통은 S dot네이밍이라고 하는데 이렇게 구현하신 이유가 궁금합니다!
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.
styled-components를 사용할 때 폴더를 분리하는 방법을 선택하신 것 같군요!
보통 style 파일을 따로 만들 때는 HeaderStyle.jsx
보단 Header.style.jsx
같은 방식을 많이 사용합니다~
setOpenCards([]); | ||
}, [level, shuffle]); | ||
|
||
function chooseCard(e, id) { |
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.
이부분도 화살표함수로 사용하면 더 좋을 것 같은데 사용하신 다른 이유가 있으신가요?
export const FrontCard = styled.article` | ||
background-image: url(${(props) => props.$img}); | ||
background-size: contain; | ||
background-repeat: no-repeat; | ||
background-position: center; | ||
`; | ||
export const BackCard = styled.article` |
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.
Semantic 태그 잘 사용하셨네요 굿굿!
|
||
export const Title = styled.h1` | ||
text-align: center; | ||
font-size: 5rem; |
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.
font-size도 theme에 정의해서 사용하면 더 유지보수 하기 편리할 것 같아요!
// 점수 업데이트 | ||
const updateScore = (newScore) => { | ||
setScore(newScore); | ||
}; | ||
// 난이도 업데이트 | ||
const updateLevel = (level) => { | ||
setLevel(level); | ||
}; | ||
// 게임 리셋 | ||
const resetGame = () => { | ||
setLevel(LEVEL[0].level); | ||
setScore(0); | ||
setShuffle(!shuffle); | ||
}; | ||
|
||
const closeModal = () => { | ||
setModalOpen(false); | ||
resetGame(); | ||
}; |
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.
깔끔하게 잘 짜셨네요 완전 가독성좋은 코드!
useEffect(() => { | ||
if (score === level) { | ||
setModalOpen(true); | ||
} | ||
}, [score]); |
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.
이 로직을 그대로 useEffect없이 updateScore 로직에 넣으면 적용이될 것 같은데요? 한번 시도해보세요~~
const updateScore = (newScore) => {
setScore(newScore);
if (newScore === level) {
setModalOpen(true);
}
};
const updateLevel = (level) => { | ||
setLevel(level); | ||
}; |
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.
level이 변경되면 score0되는 로직 여기에 넣으시면 되겠네요!
setScore(0)
추가해볼까요?
const resetGame = () => { | ||
setLevel(LEVEL[0].level); | ||
setScore(0); | ||
setShuffle(!shuffle); | ||
}; | ||
|
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.
이부분에 이전에 useEffect에서 사용하던 로직을 추가해주고 이 resetGame을 updateLevel함수에도 넣어주면 될 것 같아요!
setCards(initialCards());
setSelectedCard(null);
setMatchCards([]);
setOpenCards([]);
useEffect(() => { | ||
setCards(initialCards()); | ||
setSelectedCard(null); | ||
setMatchCards([]); | ||
setOpenCards([]); | ||
}, [level, shuffle]); |
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.
이건 연관된게 많아 확실하진 않지만 level을 변경해주는 함수인 updateLevel함수와 reset하는 함수 resetGame에 필요한 로직인 것 같네요 그러면 resetGame에만 useEffect내부 로직을 적용해주고 resetGame함수를 이곳에 호출해볼까요?
const updateLevel = (level) => {
resetGame()
setLevel(level);
};
✨ 구현 기능 명세
🧩 기본 과제
전체적인 game flow
header 구현
card section 구현
카드가 선택되었을 때 카드를 open합니다.
두 번째 카드가 클릭될 때 까지 카드는 open상태여야합니다.
두 번째 카드를 클릭하였을 때
정답일 경우 카드 두개를 open 상태로 고정하고 score를 올립니다.
오답일 경우 카드 두개를 다시 close합니다.
게임에 성공하였을 때
🔥 심화 과제
난이도 설정
게임 reset
카드 뒤집기 애니메이션
모달
공유과제
링크 첨부(팀 블로그 링크) : https://forweber.palms.blog/nowsopt-react-imddoy
📌 내가 새로 알게 된 부분
const randomElement = arr[Math.floor(Math.random() * arr.length)];
가 랜덤으로 요소를 뽑을 수 있다는 것을 알게 되었습니다.저는 이 요소들을 가지고 새로운 카드들을 만들어줘야 하기 때문에 반복문을 사용해서 새로 만들어주었습니다.
reduce 함수를 이번 기회에 처음 사용해봤습니다.
배열을 순회하면서 각 요소에 대해 콜백 함수를 실행시키고, 콜백 함수의 반환 값을 accumulator에 계속해서 누적하고 accumulator의 최종 값을 반환하는 함수였는데, 이 함수를 사용해서 새로운 카드들을 만들 수 있었습니다.
무지성으로 코드를 작성하다가 isOpen prop에서 lower이라면서 에러 메세지가 떴습니다.
HTML 어트리뷰트는 대소문자 구분이 없기 때문에 브라우저는 대문자를 소문자로 변경하여 읽어서 소문자 대문자를 혼용하기 위해서는
$
를 사용해야 한다고 해서 해결해주었습니다.💎 구현과정에서의 고민과정(어려웠던 부분) 공유!
렌더링
처음에 렌더링이 4번 되고, 첫번째 카드 클릭할 때 2번, 두번째 클릭할 때 4번이 리렌더링되는데,,,
클릭할 때 상태를 계속 바꿔서 렌더링이 되는건 이해가 되는데, 왜 처음에 렌더링이 많이 되는지 아직도 잘 모르겠습니다.
그리고 렌더링이 너무 많이 되는게 스트레스인데,,,, 아직 해결 못했습니다. 너무 어렵네요..🥲🥲
상태도 가장 적게 만든다고 작성하긴 했는데 너무 많은 것 같아서 찝찌입하네요...
id 비교
사실 처음에는 id를 중복되지 않도록 id 뒤에 level만큼 더해서 level을 나눈 나머지로 비교를 하려고 코드를 짰는데
level 선택하도록 구현을 하고 나니까 id가 잘 짝이 안맞아지더라구요... 그래서 하드코딩?으로 1000더하고 2000더하고 1000으로 나눈 나머지로 비교를 하도록 했는데 이렇게 하면 id가 잘 비교가 되는데 이유를 모르겠습니다....ㅜㅜ
🥺 소요 시간
3일
🌈 구현 결과물
https://week3-cardgame.vercel.app/