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주차 기본/공유 과제 ] 카드뒤집기 #4

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

Conversation

202010927choiminjune
Copy link
Contributor

@202010927choiminjune 202010927choiminjune commented May 1, 2024

✨ 구현 기능 명세

🧩 기본 과제

  1. 전체적인 game flow
  • 카드 두개를 오픈해서 같은 카드라면 카드를 오픈 상태에 두고 score를 올립니다.
  • score를 모두 채웠을 때 게임이 종료되고 축하 모달이 뜹니다.
  • 최초 카드 데이터는 데이터 상수 파일로 생성해서 사용합니다.
  1. header 구현

    • 페이지의 이름과 score 현황을 구현합니다
    • 카드의 짝을 맞출 때 마다 score을 상승시킵니다.
    • 최초데이터 데이터 상수 파일 생성
  2. card section 구현

    • 카드가 선택되었을 때 카드를 open합니다.

    • 두 번째 카드가 클릭될 때 까지 카드는 open상태여야합니다.

    • 두 번째 카드를 클릭하였을 때

    • 정답일 경우 카드 두개를 open 상태로 고정하고 score를 올립니다.

    • 오답일 경우 카드 두개를 다시 close합니다.

  3. 게임에 성공하였을 때

    • 축하 모달이 뜨도록 합니다.
    • 모달을 닫았을 때 카드를 모두 close하고 셔플합니다.

🔥 심화 과제

  1. 난이도 설정

    • 난이도를 선택 기능 구현
    • 난이도를 선택시 카드는 랜덤으로 배치, 셔플
    • 게임 중 난이도 변경할 시 리셋
  2. 게임 reset

    • reset버튼 fixed로 생성하여 클릭시 카드 리셋
  3. 카드 뒤집기 애니메이션

    • 카드를 open, close할 때 애니메이션 생성
  4. 모달

    • 백그라운드(back drop)를 어둡게 처리

공유과제

  • React에 대하여
  • 컴포넌트를 분리하는 기준과 방법

링크 첨부(팀 블로그 링크) : https://forweber.palms.blog/nowsopt-react-minjune

📌 내가 새로 알게 된 부분

  • ~ 부분 이렇게 구현했어요, 피드백 부탁해요!
    import { createGlobalStyle, ThemeProvider, styled } from 'styled-components'; const GlobalStyle = createGlobalStyle
    body, html {
    height: 100%;
    margin: 0;
    font-family: 'Comic Sans MS', 'Arial', sans-serif;
    background: #EED1D1;
    display: flex;
    justify-content: center;
    align-items: center;
    }`
    스타일 컴포넌트를 통해 코드의 css를 유지보수하게 쉽게 구현하는 방법을 배웠습니다.

Card.propTypes = { card: PropTypes.shape({ src: PropTypes.string.isRequired, matched: PropTypes.bool.isRequired, id: PropTypes.number.isRequired }), handleChoice: PropTypes.func.isRequired, flipped: PropTypes.bool.isRequired };

React에서 컴포넌트에 전달되는 props(속성)의 유효성을 알아보는 proptypes에 대해 알게 되었습니다. 이를 통해 개발자는 컴포넌트가 예상대로 올바른 데이터 타입의 props를 받고 있는지 확인할 수 있었으며, propTypes를 사용하는 것은 컴포넌트가 기대하는 props의 종류와 타입을 명시적으로 선언하고, 개발 과정에서 타입 오류를 조기에 발견하는 데 도움이 됩니다.

src: 문자열(PropTypes.string.isRequired)로, 카드 이미지의 URL을 나타냅니다.
isRequired는 이 프로퍼티가 반드시 제공되어야 함을 의미합니다.
matched: 불리언(PropTypes.bool.isRequired)으로, 카드가 매치되었는지 여부를 나타냅니다.
id: 숫자(PropTypes.number.isRequired)로, 카드의 고유 식별자를 나타냅니다.

💎 구현과정에서의 고민과정(어려웠던 부분) 공유!

  • ~ 부분이 잘 구현한건지 잘 모르겠어요!
  • ~부분 다른 방법이 있는지 궁금해요!

`useEffect(() => {
if (choiceOne && choiceTwo) {
if (choiceOne.src === choiceTwo.src) {
setCards(prevCards => prevCards.map(card => card.src === choiceOne.src ? { ...card, matched: true } : card));
setMatchedPairs(prev => prev + 1);
setTimeout(() => resetTurn(), 1000);
} else {
setTimeout(() => resetTurn(), 1000);
}
}
}, [choiceOne, choiceTwo]);

useEffect(() => {
if (matchedPairs ===5) {
setShowModal(true);
console.log('matchedPairs', matchedPairs);
}
console.log('cardImages.length', cardImages.length);
}, [matchedPairs]);`

카드를 맞추는 로직이 생각이 도저히 떠올리기 힘들어서 관련 레퍼런스(이전 상태값을 이용!!)를 참고했습니다. 그리고 카드의 쌍을 모달이 인식을 해야 하는데 인식을 해결할 수 없어 임의로 숫자를 부여해서 만들었습니다.

혹시 다른 분들은 어떻게 하셨는지 궁금합니다. ㅠㅠ

  1. 파일 구조를 분리를 할려 했으니 계속 실패?!하여 결국 app.jsx에 다 집어넣는 현상이 일어났는데
    이것도 역시 다른분들은 어떻게 하셨는지 궁금합니다.

const CardFace = styled.div
backface-visibility: hidden;
position: absolute;
width: 150px;
height: 150px;
border-radius: 20px;
box-shadow: 0 5px 10px ${props => props.theme.colors.shadow};
`;

const CardBack = styled(CardFace)background: ${props => props.theme.colors.primary};;

const CardFront = styled(CardFace)background: ${props => props.theme.colors.secondary}; display: flex; align-items: center; justify-content: center; font-size: 0; color: white; transform: rotateY(180deg);;`

css아직 활발하게 활용하지 못하는데 각 카드의 이미지가 모두 나오게 할려했는데 부분적으로 나왔습니다... 다른 분들은 어떻게 하셨는지 궁금합니다.

🥺 소요 시간

-7일


🌈 구현 결과물

Vite + React - Chrome 2024-05-01 14-33-56

Copy link

@seong-hui seong-hui left a comment

Choose a reason for hiding this comment

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

전체적으로 함수이름, 변수명등이 명확해서 각 역할에 대해서 잘 이해할 수 있었습니다! 긱 로직이 잘 구현되어 있는 만큼 역할에 따라서 잘 분리하면 더욱 좋은 코드가 될 것 같습니다!! 과제 내용을 잘 구현하심에 대해서 확인했고 제가 남긴 의견은 저의 의견일 뿐이니 필요하다고 생각하시는 부분만 반영하시면 될 것 같습니다. 과제 하시느라 고생 많으셨습니다 👍 👍

import { createGlobalStyle, ThemeProvider, styled } from 'styled-components';
import cardImages from './card';

console.log(cardImages);

Choose a reason for hiding this comment

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

console.log같이 디버깅을 위해서 넣어둔 코드는 지워주는 것도 좋을 것 같아요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

margin-bottom: 20px;
`;

const Modal = styled.div`

Choose a reason for hiding this comment

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

modal에 해당하는 컴포넌트는 따로 만들어서 가져오는 방식으로 구현하는 건 어떨까요? 현재 app 컴포넌트가 길고, 여러 기능을 담당하고 있습니다. 코드의 가독성과 유지보수성을 높이기 위해, 컴포넌트를 더 작은 단위로 분리하고 해당 기능에 맞게 역할을 분배하는 것은 어떨까요?

Choose a reason for hiding this comment

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

저도 성희님 말에 동의합니다! modal, card, header, button 등등.. 컴포넌트를 작은 단위로 분리하고 사용하면 훨씬 더 가독성있는 코드가 될 거 같아요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

gap: 16px;
`;

function Card({ card, handleChoice, flipped }) {

Choose a reason for hiding this comment

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

모달 컴포넌트 분리때와 마찬가지로 카드라는 컴포넌트도 따로 분리해서 구현하는 방식이라면 유지 보수 측면에서 더욱 좋을 것 같습니다!!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.


useEffect(() => {
if (choiceOne && choiceTwo) {
if (choiceOne.src === choiceTwo.src) {

Choose a reason for hiding this comment

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

카드가 일치하는지의 여부를 이미지의 주소로 확인하고 있는데 이것도 좋지만 카드의 데이터에 이미지와 함께 id를 부여해서 카드의 일치를 확인하면 더욱 좋을 것 같아요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

}, [choiceOne, choiceTwo]);

useEffect(() => {
if (matchedPairs ===5) {

Choose a reason for hiding this comment

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

매직 넘버보다는 상수를 이용해서 수를 관리하면 나중에 숫자를 바꿀때도 상수의 내용만 바꾸면 되니 더욱 유용할 것 같아요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 다음에 상수 한번 이용하겠습니다~ 감사합니다.

.map((card, index) => ({ ...card, id: index, flipped: false }));

// 게임 상태를 초기화합니다.
setChoiceOne(null);

Choose a reason for hiding this comment

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

게임 상태를 초기화 하는 코드를 따로 함수로 빼서 initCards와 같이 만들어서 호출하는 건 어떨까요? 따로 관리하면 나중에 여기가 아닌 다른 곳에서 초기화를 하는 경우에도 잘 사용할 수 있을 것 같아요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

Copy link

@cindy-chaewon cindy-chaewon left a comment

Choose a reason for hiding this comment

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

컴포넌트와 스타일 파일을 만들어서 적용시키면 더더 좋은 코드가 될 거 같아요! 과제하느라 고생하셨습니다!!

Comment on lines 64 to 72
const CardFront = styled(CardFace)`
background: ${props => props.theme.colors.secondary};
display: flex;
align-items: center;
justify-content: center;
font-size: 0;
color: white;
transform: rotateY(180deg);
`;

Choose a reason for hiding this comment

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

pr에서 질문주신 카드 css 에 관한 답변입니다..!
background-size: cover; // 이미지가 카드 크기에 맞게 조절 background-position: center; // 이미지가 카드 중앙에 위치
이 두가지 속성 넣으면 이미지가 카드 크기에 맞게 나올 겁니다..!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

`;

const CardFront = styled(CardFace)`
background: ${props => props.theme.colors.secondary};

Choose a reason for hiding this comment

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

이 부분은 카드 색상 지정하는 거 같은데, 카드에 이미지가 표시되니 지워도 되지 않을까요?!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

margin-bottom: 20px;
`;

const Modal = styled.div`

Choose a reason for hiding this comment

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

저도 성희님 말에 동의합니다! modal, card, header, button 등등.. 컴포넌트를 작은 단위로 분리하고 사용하면 훨씬 더 가독성있는 코드가 될 거 같아요!

Comment on lines 8 to 32
const GlobalStyle = createGlobalStyle`
body, html {
height: 100%;
margin: 0;
font-family: 'Comic Sans MS', 'Arial', sans-serif;
background: #EED1D1;
display: flex;
justify-content: center;
align-items: center;
}
`;

const theme = {
colors: {
primary: '#FFC1C1',
secondary: '#FFF3F3',
text: '#333',
modalBackground: 'rgba(0, 0, 0, 0.5)',
white: 'white',
shadow: 'rgba(0,0,0,0.1)'
},
fonts: {
main: 'Comic Sans MS, Arial, sans-serif'
}
};

Choose a reason for hiding this comment

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

global style 부분과 theme 부분도 따로 파일로 빼서 적용하면 좋을 거 같아요! 관련자료 첨부해요!
https://oliviakim.tistory.com/120

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

Copy link

@se0jinYoon se0jinYoon left a comment

Choose a reason for hiding this comment

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

성희랑 채원이가 너무 잘 달아주어서 추가적으로 말할 부분들만 달아두었어요!
성희랑 채원이가 단 부분들은 저도 다 동의하는 부분들이니 한 번 잘 찾아보고 적용해보면 좋을 것 같아요!

로직 짜는 건 너무너무 잘 해주었는데, 컴포넌트를 별도의 파일로 분리해서 관리해야하는 상태들을 prop으로 내려주는 부분을 더 공부해보면 좋을 것 같아요

컴포넌트를 어떻게 분리해야할지 감이 잘 잡히지 않을 때는

  1. 뷰에서 담당하는 부분이 확실하게 존재하는지 (Header, GameMain, Modal 등)
  2. 해당 부분에서 관리하는 상태, 데이터값이 얼마나 겹치는지 ! (GameMain과 Header의 경우에는 공통으로 사용되는 데이터값이 많죠 ? 예를 들면 전체 카드 배열 길이라던가, 매칭된 카드 배열 길이라든가 .. )
  3. 이러한 컴포넌트들을 공통으로 감쌀 수 있는 부모가 있는지, (현재는 App이 되겠죠?) 있다면 그 부모 컴포넌트에서 관련된 데이터들을 다루고, 필요한 컴포넌트들에 prop으로 내려주어 사용하면 되겠죠?
    이런식으로 컴포넌트를 분리해나가는 과정을 공부해보면 좋을 것 같아요!
    과제하느라 너무너무 수고 많았습니다~

const CardFace = styled.div`
backface-visibility: hidden;
position: absolute;
width: 150px;

Choose a reason for hiding this comment

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

px말고 rem 사용하기!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

Comment on lines 135 to 136
const [choiceOne, setChoiceOne] = useState(null);
const [choiceTwo, setChoiceTwo] = useState(null);

Choose a reason for hiding this comment

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

이 부분을 하나의 배열 state로 만들어서 사용해도 좋을 것 같아요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

Comment on lines +18 to +28
/* 게임 상태 바 */
.game-status-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
background-color: #FFF3F3;
border-radius: 10px;
box-shadow: 0 5px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
}

Choose a reason for hiding this comment

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

이 파일에 있는 css는 안쓰이고 있는 것 같은데 맞나요 ? 안쓰인다면 지워줘도 될 것 같아요!
그리고 styled-component를 사용하는 만큼, 스타일 관련 코드는 컴포넌트화해서 사용하는게 좋을 것 같습니다~

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

Comment on lines 122 to 130
Card.propTypes = {
card: PropTypes.shape({
src: PropTypes.string.isRequired,
matched: PropTypes.bool.isRequired,
id: PropTypes.number.isRequired
}),
handleChoice: PropTypes.func.isRequired,
flipped: PropTypes.bool.isRequired
};

Choose a reason for hiding this comment

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

이 부분은 타입스크립트를 사용하게되면 사용하지 않아도 되는 부분이랍니다 ! 참고!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다~

Copy link
Contributor

@ljh0608 ljh0608 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,13 @@
<!doctype html>
<html lang="en">
Copy link
Contributor

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>
Copy link
Contributor

Choose a reason for hiding this comment

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

title역시 SEO에 굉장히 중요합니다! page에 알맞은 title로 변경해주세요!

Comment on lines +32 to +38
.game-status-bar h1 {
margin: 0;
font-size: 1.5em;
color: #333;
}

.game-status-bar button {
Copy link
Contributor

Choose a reason for hiding this comment

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

이렇게 사용하면 game-status-bar 내부에 버튼이 여러개 존재할 경우 예기치 않은 오류가 발생할 수 있습니다!
button역시 className을 부여해서 사용해주세요

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants