-
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주차 기본/심화 과제] 🌼 짱구는 못말려! 폭풍을 부르는 카드게임 대격돌 💖 #8
Conversation
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.
본격적인 리뷰 전에 일단 넘넘 수고 많았다구 넘 잘하구 있다고 말해주고 싶어여..!!!! 리액트가 익숙하지 않으면 정말 어려웠을텐데 끝까지 포기하지 않고 해낸 모습이 넘 멋찌다!!!!! 배포까지 야무지게 하구 ㅎㅎ QA 해 봤는데 같은 카드를 두 번 연속 클릭하면 뒤집혀진 상태가 유지되는 오류가 있어서 요것 한 번 수정해 보면 좋겠다!!! 짱구 배경음악은 어떻게 넣을 생각을 했지..? 진짜 똑똑이 그 자체,,,💞💞 저랑 같이 열심히 리팩토링 해 보아요 😄🌸🐰
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.
context나 커스텀 훅에 대해서 안 써봤는데, 덕분에 이렇게 쓰는 거구나를 깨닫고 가 ~!
또 지역/전역 상태 관리를 통해서 컴포넌트를 다루는 과정에서 다양한 방법이 있는 걸 알고
좀 더 내 코드를 다른 방면으로 수정해볼 수도 있겠다는 생각이 든다!!
수고했어 은서 ㅎㅎ 디자인도 최고 !!
우리 금잔디조 폼 미쳐따이
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.
미쳐써 미쳐써 열정 미쳐써....
// 카드가 뒤집어지는 잠깐의 시간동안 새로 달라진 카드가 노출되지 않게 하기 위해, 카드 목록이 바뀔 때까지 약간의 딜레이를 준다. | ||
setTimeout(() => { | ||
setCardAllList(getCardArr(levelType)); | ||
}, 800); |
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.
이거 정말 디테일하다!!
default: | ||
return 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.
디폴트는 이지로 해준 것도 엄청 꼼꼼해~~
|
||
const score = useContext(ScoreContext); | ||
|
||
// 첫 렌더링 때는 useEffect를 적용하지 않도록 custom hook을 사용함! |
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를 조정할 수 있구나 -!!!
import { useRef } from "react"; | ||
import { useState } from "react"; | ||
|
||
const Button = (props) => { |
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.
하나의 Button 컴포넌트를 만들어두고, 한 파일 안에서 재사용할 수도 있구나!
근데 궁금한 점은 이렇게 한 파일에서 여러 컴포넌트를 만들어서 사용하는 경우가 자주 있나 궁금해~
나는 이번에 거의 한 파일에 한 컴포넌트만 넣어줬는데, 너무 잘게 분리되면 관리가 어렵다는 단점이 있겠다는 생각도 들고!
뭐가 더 나은 접근 방식일까???
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.
맞아 나도 고민이 됐는데... 일단 나는 Button 컴포넌트가 이 파일 외에 다른데서는 쓰이지 않으니 이 안에 묶어두었어
그런데 만들다보면 다른데 쓰이는 상황이 생길수도 있으니까 초기에 설계를 잘해야될 것 같아!
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.
만약 Button 이 다른 곳에서도 공통으로 쓰인다면 common 폴더를 만들고 그 안에 파일을 만들어서 쓰는 게 좋을 것 같구 그게 아니라면 그냥 메인이 되는 것 하위에 styled component 로 버튼을 만드는 것이 더 깔끔할 것 같긴 해!! 아무래도 컴포넌트가 여러 개면 뭐가 메인인지 찾는데 시간도 걸리고 하니까..!!! :)
const Buttons = (props) => { | ||
const { setCompareList, setPairedList, setCardAllList } = props; | ||
|
||
return ( | ||
<> | ||
<ButtonContainer> | ||
<LevelButtons | ||
setCompareList={setCompareList} | ||
setPairedList={setPairedList} | ||
/> | ||
<RightGroupWrapper> | ||
<Score /> | ||
<ResetButton | ||
setCompareList={setCompareList} | ||
setPairedList={setPairedList} | ||
setCardAllList={setCardAllList} | ||
/> | ||
</RightGroupWrapper> | ||
</ButtonContainer> | ||
</> | ||
); | ||
}; | ||
|
||
export default Buttons; |
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 에 써준대로 버튼이라는 컴포넌트치고는 점수라는 다른 컴포넌트도 얽혀있는 느낌이 들긴 한다 🥹
Button 컴포넌트들을 Main에선 LevelButtons을, Header(예시)에선 Score, ResetButton 이렇게 따로 렌더링해도 좋았을 것 같은 느낌~!
혹시 한 컴포넌트에서 export {LevelButtons, ResetButton} 이런식으로 내보내 줄수도 있나 궁금하네 !! (뭔가 안될 것 같은 느낌이지만)
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.
우앗 개선 방식 같이 고민해줘서 고마워 ㅎㅎ
앗 맞아 한 파일에서 여러개의 컴포넌트를 내보낼 수 있더라! 대신 default 는 한 모듈당 하나만 있어야한대
switch (compareList.length) { | ||
case 0: // 첫번째 선택 | ||
tempCompareList = [...compareList]; | ||
tempCompareList.push({ pk, imgId }); |
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.
나는 이미지 url과 고유한 이미지 키를 관리하기 위해 두가지 상태를 썼는데,
이렇게 한 객체로 묶어서 하나의 상태로 관리해주는 법도 있다는 걸 알아가 ~!
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,value 쌍으로 넣으면 좀 더 명확할거같당
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.
미쳐써 미쳐써 열정 미쳐써....
); | ||
}; | ||
|
||
export default Audio; |
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 { useRef } from "react"; | ||
import { useState } from "react"; | ||
|
||
const Button = (props) => { |
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.
만약 Button 이 다른 곳에서도 공통으로 쓰인다면 common 폴더를 만들고 그 안에 파일을 만들어서 쓰는 게 좋을 것 같구 그게 아니라면 그냥 메인이 되는 것 하위에 styled component 로 버튼을 만드는 것이 더 깔끔할 것 같긴 해!! 아무래도 컴포넌트가 여러 개면 뭐가 메인인지 찾는데 시간도 걸리고 하니까..!!! :)
}; | ||
|
||
const ResetButton = (props) => { | ||
const { setCompareList, setPairedList, setCardAllList } = props; |
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.
컴포넌트로 분리시키니까 이런 setState 하나하나 props 로 받아와야해서 좀 번거로운 감이 있다..!! 한 번 하나의 함수 내에서 styled component 로 만드는 것 시도해 보면 좋을 것 같아!! :)
return ( | ||
<StyledCardWrapper> | ||
<div className="pair"> | ||
<div className={`card ${isComparing || isPaired ? "flipped" : ""}`}> |
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.
flipped 여부를 판단하는 state 를 만들어봐도 좋을 것 같댜!!! :)
🔗 구현 페이지
✨ 구현 기능 명세
기본 과제
게임 난이도 선택
종답 수 노출
카드 선택
카드 배열 순서
심화 과제
애니메이션
theme + styled-components 적용
게임 초기화 버튼
createPortal
🌼 PR Point
폴더 구조
기능 하나하나를 만들 때마다 어떤 파일을 어느 폴더에 넣을지 결정하는게 참 어려웠어요! 결과적으로 이런 폴더 구조가 나왔는데 어떤가요? 사실 컴포넌트 구조를 전혀 생각하지 않고 시작해서 모듈화가 제대로 안되었어요 ㅠㅠ
상태 관리
저는 전역 상태 관리를 하는게 뭔가 발전된(?) 형태인 줄 알고 처음에 그렇게 시작을 했는데, 웨비들 말을 들어보니 그래도 처음엔 prop drilling 부터 해보는게 학습에 있어서는 더 좋다고 하더라구요!! 그래서 그말을 듣고 prop을 내려주는 방식으로 구현했습니다. 다들 뭔가 전역으로 관리하는게 편법이라는 식으로 말하든데 저는 아직 그게 더 어렵네요;;
그래서 결과적으로 난이도(level)와 점수(score)는 contextAPI+useReducer를 통해 전역으로 관리했고, 나머지는 전부 prop으로 넘겨주었습니다. 근데 생각과제 하면서 보니까 contextAPI는 변동이 많은 데이터에는 적합하지 않다네요..? 아차차.... 아무튼 그래도 prop이 아닌 다른 상태 관리 방식을 사용해볼 수 있는 좋은 기회였습니다! (context/context.jsx)에 있습니다!
난이도에 따른 랜덤 카드 추출
이 부분을 4가지 함수로 구현했어요!
utils/getCardArr.js
가져올 이미지의 인덱스를 랜덤으로 선택하기 위해, 랜덤한 숫자 리스트를 생성하는 함수입니다.
같은 카드를 두장씩 보여줘야하므로 array의 요소를 두개씩 복제한 뒤 랜덤으로 섞는 함수입니다.
random() 메소드에는 편향이 있다는 글을 보고 다른 방식을 찾아서 shuffle 함수를 따로 구현하였습니다! 출처
이것이 최종적으로 난이도를 인자로 받아서 카드 목록을 반환해주는 함수입니다!
Card 컴포넌트
카드의 state에 따라서 style을 변경해주어야 하기 때문에, 특정 state에 따라서 카드의 뒤집히는 스타일링과 관련된 className을 넣어서 해결했습니다.
components/Card.jsx
compareList
가 현재 비교대상에 해당하는 카드 목록이고,pairedList
는 이미 정답을 맞춘 카드 목록입니다. 이 상태에 해당하는 카드들은 앞면으로 뒤집어주어야 하기 때문에,${isComparing || isPaired ? "flipped" : ""}
라는 구문에서 "flipped"라는 className을 붙여주게 됩니다!Cards 컴포넌트
Card 컴포넌트를 매핑하여 최종적으로 전체 카드 리스트를 관리하는 가장 핵심적인 부분입니다.
카드 클릭 핸들링
compareList
와pairedList
라는 두가지 상태로 카드의 상태를 관리합니다.compareList에 아무것도 들어있지 않다면 아직 선택한 카드가 없다는 뜻이에요. 이 상태에서 카드를 클릭하게 되면 첫번째로 고른 카드이므로 compareList에 추가합니다.
두번째 선택을 했을 때는 비교가 필요합니다. 따라서 imgId(이미지에 따라 부여한 아이디)를 비교하여 정답이라면 score에 대한 dispatch를 통해 점수를 올리고, 짝이 지어진 쌍을 pairedList로 옮깁니다!! 그리고 비교가 끝났으므로 compareList에서는 삭제해주는 겁니다!!!
만약 오답이라면 setTimeout으로 800ms의 시간이 지난 뒤 compareList에서 해당 카드들을 삭제합니다. 이렇게하면 800ms 뒤에 카드가 다시 뒤집히게 됩니다.
자, 다시 보시면, Card 컴포넌트에서는 현재 이 카드가 비교되고 있는 카드인지 아니면 이미 정답이 맞춰진 카드인지를 알기 위해서 해당 카드의 pk라는 요소를 참조하고 있고, Cards 컴포넌트에서는 같은 이미지인지 아닌지를 비교하기 위해서 해당 카드의 imgId라는 요소를 참조하고 있습니다. 즉, 한 카드에 대한 고유한 키(pk)와 이미지를 나타내는 아이디(imgId)가 둘다 필요했던 것입니다. 따라서 저는
compareList
와pairedList
라는 상태에 해당 카드의 {pk, imgId} 쌍을 넣음으로써 두가지 정보를 사용할 수 있었습니다.점수에 따른 애니메이션
이 부분은 거의 유일하게(?) 다른 코드를 참고하지 않고 제가 고안한 로직인데요...ㅋㅋㅋ 그래서 다른 분들은 어떻게 구현했는지 궁금!!
일단 score를 주시하고 있다가 이게 변하면 애니메이션을 추가해야되는거니까, 세미나에서 배웠던 useRef와 useEffect를 활용해봤습니다! 그래서 score를 나타내는 부분에 animationScore라는 ref를 추가해서 선택해주고, useEffect를 통해 score라는 값이 변경되면
animationScore.current.classList.toggle("animiation")
을 통해 animation이라는 className을 추가함으로써 애니메이션을 넣을 수 있었습니다. 그런데 이 때 처음 마운트 될 때도 애니메이션이 발생하는걸 막기 위해서 custom hook을 만들어 사용했습니다. (hooks/useDidMountEffect.js에 있어요!) 참고 (근데 서현이 PR 보니깐 score가 0이면 그냥 return해버리는 방법이 있네요.. ㄴㅇㄱ)자랑하고 싶은 디테일
배경 음악
들어가보시면 왼쪽 상단에 플레이어가 하나 있어요~ 짱구 오프닝을 넣어봤답니다~ 자동재생하고 싶어서 이것저것 엄청 찾아봤는데 브라우저 자체에서 차단하고 있는거라 해결하기가 어렵더라구요ㅠㅠ 그래서 일단 플레이어를 넣는걸로 해결~~난이도 변경, 리셋 시 카드 목록 변경에 딜레이 주기
그니깐 난이도 변경이나 리셋을 하면 카드가 다시 뒤집히면서 초기화되잖아요? 근데 다시 뒤집힐때도 뒤집히는 애니메이션이 생기는데 그 뒤집히는 잠깐의 찰나동안 새로 바뀐 카드 앞면이 노출이 되더라구요... 처음엔 그냥 난이도 변경이나 리셋시에는 애니메이션이 적용되지 않도록 하고싶었는데 좀 복잡해지는 것 같아서, 선택한 방법은 setTimeout으로 카드 목록 변경에 딜레이를 주는 것이었습니다. 그러니까 카드가 뒤집히는 애니메이션이 끝났을 때 비로소 카드가 변경되어요.디자인 귀엽죠!!!
디자인에 몇 시간을 쓴거지...🥺 소요 시간, 어려웠던 점
30h
제가 이번에 시간을 재면서 해봤거든요...? 진짜 30시간이나 걸렸어요... 🤣😇🤢🥹 많이 부족한만큼 열심히 했습니다 더 성장하겠어요!!!이거 오버레이 화면 높이 꽉 채우는거 대체 어떻게 하는거죠...? 100vh나 100%로 하면 그냥 딱 화면 높이만큼만 되어서 스크롤을 하면 밑에 빈 공간이 생겨요ㅠㅠ -> position:fixed로 해결!!!! 현수가 알려줬숩니다 고마워요
🌈 구현 결과물
🔗 구현 페이지
게임 플레이
_AdobeExpress.2.mp4
리셋 기능 (카드 목록이 변경된다)
_AdobeExpress.mp4