-
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주차 기본/생각 과제] 닮은 동물 찾기 #6
base: main
Are you sure you want to change the base?
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.
이번 과제도 너무 수고 많았어 !!~
컴포넌트 분리에 초점 맞춰서 리팩토링 해보면 좋을 것 같아 !
그리구 구현한 컴포넌트들에 비슷한 코드들이 중복되는 경우가 꽤 있는 것 같은데,
공통 함수로 빼주거나 props를 내려주는 등 중복코드를 제거하는 방향으로 리팩토링 해보는걸 추천할게 !
.question-box { | ||
display: flex; | ||
justify-content: center; | ||
margin-bottom: 1rem; | ||
} |
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 파일 없이 styled-components만으로도 원하는 스타일을 구현할 수 있어!
처음에 다현이가 스타일이 제대로 먹히지 않는 코드가 뭐였는지 정확히는 모르겠지만,
예를 들어 정의하고자하는 스타일 컴포넌트의 이름이 questionBox라고 한다면 아래처럼 구현해줄 수 있어 !
const questionBox: styled.div `
display: flex;
justify-content: center;
margin-bottom: 1rem;
`
공통으로 스타일을 주고 싶은 부분은 theme과 globalStyle을 구현하면 쉽게 정의해줄 수 있어!
과제에 필요한 대부분의 개념이나 구현 방식은 각 주 세미나 자료에 잘 정리되어있으니까 참고해보면 도움 많이 될거야 !
const animalData = [ | ||
{ answer: [A, A, A], animal: "강아지", imgUrl: "/img/1.png" }, | ||
{ answer: [A, A, B], animal: "고양이", imgUrl: "/img/2.png" }, | ||
{ answer: [A, B, A], animal: "오랑우탄", imgUrl: "/img/3.png" }, | ||
{ answer: [A, B, B], animal: "쿼카", imgUrl: "./img/4.png" }, | ||
{ answer: [A, C, A], animal: "토끼", imgUrl: "./img/5.png" }, | ||
{ answer: [A, C, B], animal: "붕어", imgUrl: "./img/6.png" }, | ||
{ answer: [B, A, A], animal: "팬더", imgUrl: "./img/7.png" }, | ||
{ answer: [B, A, B], animal: "비버", imgUrl: "./img/8.png" }, | ||
{ answer: [B, B, A], animal: "얼룩말", imgUrl: "./img/9.png" }, | ||
{ answer: [B, B, B], animal: "돌고래", imgUrl: "./img/10.png" }, | ||
{ answer: [B, C, A], animal: "쥐", imgUrl: "./img/11.png" }, | ||
{ answer: [B, C, B], animal: "미어캣", imgUrl: "./img/12.png" }, | ||
{ answer: [C, A, A], animal: "용", imgUrl: "./img/13.png" }, | ||
{ answer: [C, A, B], animal: "기린", imgUrl: "./img/14.png" }, | ||
{ answer: [C, B, A], animal: "곰", imgUrl: "./img/15.png" }, | ||
{ answer: [C, B, B], animal: "펭귄", imgUrl: "./img/16.png" }, | ||
{ answer: [C, C, A], animal: "사자", imgUrl: "./img/17.png" }, | ||
{ answer: [C, C, B], animal: "오리", imgUrl: "./img/18.png" }, | ||
]; |
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.
보통 상수를 선언해줄 때는 UPPER_SNAKE_CASE로 ANIMAL_DATA
선언해줘 !
지금은 다현이가 모든 기능이나 상태를 App.jsx에서 한 번에 정의해줬지만 사실은 react의 특성에 맞게 컴포넌트를 분리해서 구현해야 하는데, 이때 하나의 상수 데이터가 여러 컴포넌트에서 공통적으로 사용되거나 상수 데이터의 길이가 너무 길어질 경우에는 별도의 JS 파일로 분리해서 사용해주는게 좋아!
그리고 다현이가 생각과제에 언급해준 것처럼, 리팩토링때는 하나의 컴포넌트가 하나의 일만 할 수 있도록 최대한 컴포넌트를 분리해주자!
컴포넌트는 어떤 기준과 방법으로 분리하는 것이 좋을까?
- 한 컴포넌트가 하나의 일만 하게 분리하는 것이 좋다고 한다
3주차/FindAnimal/src/App.css
Outdated
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.
안 쓰는 파일들은 모두 삭제해주기 !
); | ||
} | ||
|
||
function PageRandom({ animalData }) { |
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.
지금 여기 컴포넌트들은 정의를 해줬지만, 컴포넌트 분리없이 App.jsx에서 모든 기능을 구현한 걸로 보여져!
세미나 자료나 실습시간에도 컴포넌트를 모두 분리해서 해줬던 걸로 기억하는데, 요렇게 구현한 이유가 따로 있을까?
컴포넌트는 재사용할 수 있는 최소 UI 단위지만, 웹의 복잡도와 해당 컴포넌트에서 수행하려고 하는 역할에 따라 얼마든지 복잡해질 수 있다고 해. 그래서 복잡한 코드를 비슷한 기능을 하는 코드로 묶어서 별도로 관리해야 컴포넌트를 본래 목적에 맞게 잘 사용할 수 있는 것 같아 !
컴포넌트 분리의 기준은 각자마다 다르겠지만, 혼자 구현하는 경우라도 확실한 기준을 잡고 나눠주는게 좋다고 생각해!
정안오빠 코드리뷰에서도 언급한 부분이기도 한데, 협업을 하게되면 App.jsx는 모두가 공통적으로 접근하고 수정하는 공통파일이 될 거야. 이 파일 안에 내가 구현하고자 하는 기능에 필요한 상태나 함수, 컴포넌트들을 모두 넣어둔다면 + 모든 사람들이 App.jsx 안에서 기능을 구현한다면 !? 매번 기능을 구현할 때마다 깃 충돌이 발생하게 되겠지 ..! 본인이 구현한 코드를 찾기도, 그에 따라 유지보수를 진행하기도 어려워질 가능성이 매우매우 높아져!
다현이도 이 부분 꼭 참고해줬으면 좋겠고, 리팩토링 때는 컴포넌트를 분리하는 걸 추천할게 !
const timer = setInterval(() => { | ||
if (countdown > 0) { | ||
setCountdown(countdown - 1); | ||
} else { | ||
clearInterval(timer); | ||
const randomAnimalIndex = Math.floor(Math.random() * 18); | ||
setDisplayAnimal(animalData[randomAnimalIndex].animal); | ||
setDisplayPic(animalData[randomAnimalIndex].imgUrl); | ||
} | ||
}, 1000); |
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.
cleanup 함수로 clearInterval(timer)를 해주고 있으니까, else 문에서 한 번 더 clearInterval()을 정의해주지 않고 요런 식으로 구현할 수 도 있겠다 ..!
const timer = setInterval(() => { | |
if (countdown > 0) { | |
setCountdown(countdown - 1); | |
} else { | |
clearInterval(timer); | |
const randomAnimalIndex = Math.floor(Math.random() * 18); | |
setDisplayAnimal(animalData[randomAnimalIndex].animal); | |
setDisplayPic(animalData[randomAnimalIndex].imgUrl); | |
} | |
}, 1000); | |
if (countdown > 0) { | |
const timer = setInterval(() => { | |
setCountdown(countdown - 1); | |
} else { | |
const randomAnimalIndex = Math.floor(Math.random() * 18); | |
setDisplayAnimal(animalData[randomAnimalIndex].animal); | |
setDisplayPic(animalData[randomAnimalIndex].imgUrl); | |
} | |
}, 1000); |
<span> | ||
<NavigateButton onClick={handleGoPage1}>back</NavigateButton>{" "} | ||
</span> | ||
<span> | ||
<NavigateButton onClick={handleGoPage2} disabled={!buttonActive}> | ||
next | ||
</NavigateButton>{" "} |
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.
컴포넌트 분리를 한 상태라고 가정했을 때, 나 같은 경우는 단계별 컴포넌트를 관리하는 가장 상위 컴포넌트에서 이전/다음 버튼을 정의해주고, 하위에 있는 단계 별 컴포넌트에서는 이전/다음 버튼을 따로 정의하지 않아도 되게 구현해봤어!
요 부분 내 코드 한 번 참고해봐도 좋을 것 같당 !
3주차까지 오느라 고생했어 다현아. 전체적으로 코드를 쭉 읽어봤을 때, 나도 아름이랑 똑같이 컴포넌트 분리를 해보면 어떨까 생각해! 난 page1,2,3 로 각각 나누고, App.js에서 모든 컴포넌트를 관리한다고 생각하고 접근했었거든 ! 내 코드가 도움이 될지는 모르겠지만, 접근 방식 자체는 참고해볼만 할 것 같아. |
} | ||
|
||
function Page1({ goPage2, goPage0 }) { | ||
const [selectedOption, setSelectedOption] = useState(null); |
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.
selectedOption 같은 경우에는 여러 컴포넌트에서 전부 사용되고 있으니깐, App에서만 관리하고 하위 컴포넌트로 내려주는 방식은 어떨까??
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.
음 이부분은 그 단계에서 선택한 option이 저장되는 부분이야!! 나는 단계별로 option이 총 3번, 배열로 저장되고, 그 배열을 기존에 있는 data랑 비교해서 결과를 보여주는 형태로 구현한거거든!
그 단계에서 선택한 option을 저장하고 다음으로 넘어가는 버튼을 누를 때, 배열에 추가해주려고 저렇게 짰어
const animalData = [ | ||
{ answer: [A, A, A], animal: "강아지", imgUrl: "/img/1.png" }, | ||
{ answer: [A, A, B], animal: "고양이", imgUrl: "/img/2.png" }, | ||
{ answer: [A, B, A], animal: "오랑우탄", imgUrl: "/img/3.png" }, | ||
{ answer: [A, B, B], animal: "쿼카", imgUrl: "./img/4.png" }, | ||
{ answer: [A, C, A], animal: "토끼", imgUrl: "./img/5.png" }, | ||
{ answer: [A, C, B], animal: "붕어", imgUrl: "./img/6.png" }, | ||
{ answer: [B, A, A], animal: "팬더", imgUrl: "./img/7.png" }, | ||
{ answer: [B, A, B], animal: "비버", imgUrl: "./img/8.png" }, | ||
{ answer: [B, B, A], animal: "얼룩말", imgUrl: "./img/9.png" }, | ||
{ answer: [B, B, B], animal: "돌고래", imgUrl: "./img/10.png" }, | ||
{ answer: [B, C, A], animal: "쥐", imgUrl: "./img/11.png" }, | ||
{ answer: [B, C, B], animal: "미어캣", imgUrl: "./img/12.png" }, | ||
{ answer: [C, A, A], animal: "용", imgUrl: "./img/13.png" }, | ||
{ answer: [C, A, B], animal: "기린", imgUrl: "./img/14.png" }, | ||
{ answer: [C, B, A], animal: "곰", imgUrl: "./img/15.png" }, | ||
{ answer: [C, B, B], animal: "펭귄", imgUrl: "./img/16.png" }, | ||
{ answer: [C, C, A], animal: "사자", imgUrl: "./img/17.png" }, | ||
{ answer: [C, C, B], animal: "오리", imgUrl: "./img/18.png" }, | ||
]; |
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 [showPage0, setShowPage0] = useState(true); | ||
const [showPage1, setShowPage1] = useState(false); | ||
const [showPage2, setShowPage2] = useState(false); | ||
const [showPage3, setShowPage3] = useState(false); | ||
const [showPage4, setShowPage4] = useState(false); |
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.
오빠 말대로 하니까 훨씬 간단해진다 ㅋㅋㅋㅎ
handleOptionChange("웅!"); | ||
}} | ||
> | ||
{" "} | ||
응!! | ||
</Question> | ||
<Question onClick={() => handleOptionChange("반반")}> 반반</Question> | ||
<Question onClick={() => handleOptionChange("아니ㅠ")}> |
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.
이미 상수로 선언해놨으니까 그거 변수명만 리팩토링해서 가져다쓰면 되겠다~!
function Page2({ goPage3, goPage1 }) { | ||
const [selectedOption, setSelectedOption] = useState(null); |
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.
과제 고생했어 언니!! 아름언니가 리뷰를 너무 잘 달아줬지만, 리액트에서 핵심은 컴포넌트인 만큼, 컴포넌트를 분리해서 코드를 짜는 건 너무너무나도 중요해!! 반드시 반드시 다른 코드 참고해서 컴포넌트 분리하는 리팩토링 해보면 좋겠어!! 🍀
const restartPage1 = () => { | ||
setShowPage1(true); | ||
setShowPage0(false); | ||
setShowPage2(false); | ||
setShowPage3(false); | ||
setShowPage4(false); | ||
setSelectedOptions([]); | ||
}; | ||
|
||
const goPage0 = (option) => { | ||
setShowPage0(true); | ||
setShowPage1(false); | ||
setShowPage2(false); | ||
setShowPage3(false); | ||
setShowPage4(false); | ||
}; | ||
|
||
const goPage1 = (option) => { | ||
setShowPage0(false); | ||
setShowPage1(true); | ||
setShowPage2(false); | ||
setShowPage3(false); | ||
setShowPage4(false); | ||
setSelectedOptions([...selectedOptions, option]); | ||
}; | ||
|
||
const goPage2 = (option) => { | ||
setShowPage0(false); | ||
setShowPage1(false); | ||
setShowPage2(true); | ||
setShowPage4(false); | ||
setShowPage3(false); | ||
setSelectedOptions([...selectedOptions, option]); | ||
}; | ||
|
||
const goPage3 = (option) => { | ||
setShowPage0(false); | ||
setShowPage1(false); | ||
setShowPage2(false); | ||
setShowPage3(true); | ||
setShowPage4(false); | ||
setSelectedOptions([...selectedOptions, option]); | ||
}; | ||
|
||
const goPage4 = (option) => { | ||
setShowPage0(false); | ||
setShowPage1(false); | ||
setShowPage2(false); | ||
setShowPage3(false); | ||
setShowPage4(true); | ||
setSelectedOptions([...selectedOptions, option]); | ||
}; |
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 [showPage, setShowPage]= useState(0); 이런식으로 하나의 상태를 0,1,2로 관리할 수 있다면, 이 함수들도
const restartPage1 = () => { | |
setShowPage1(true); | |
setShowPage0(false); | |
setShowPage2(false); | |
setShowPage3(false); | |
setShowPage4(false); | |
setSelectedOptions([]); | |
}; | |
const goPage0 = (option) => { | |
setShowPage0(true); | |
setShowPage1(false); | |
setShowPage2(false); | |
setShowPage3(false); | |
setShowPage4(false); | |
}; | |
const goPage1 = (option) => { | |
setShowPage0(false); | |
setShowPage1(true); | |
setShowPage2(false); | |
setShowPage3(false); | |
setShowPage4(false); | |
setSelectedOptions([...selectedOptions, option]); | |
}; | |
const goPage2 = (option) => { | |
setShowPage0(false); | |
setShowPage1(false); | |
setShowPage2(true); | |
setShowPage4(false); | |
setShowPage3(false); | |
setSelectedOptions([...selectedOptions, option]); | |
}; | |
const goPage3 = (option) => { | |
setShowPage0(false); | |
setShowPage1(false); | |
setShowPage2(false); | |
setShowPage3(true); | |
setShowPage4(false); | |
setSelectedOptions([...selectedOptions, option]); | |
}; | |
const goPage4 = (option) => { | |
setShowPage0(false); | |
setShowPage1(false); | |
setShowPage2(false); | |
setShowPage3(false); | |
setShowPage4(true); | |
setSelectedOptions([...selectedOptions, option]); | |
}; | |
const changePage = (page, option) => { | |
setShowPage(page); | |
if (page === 0) { | |
setSelectedOptions([]); | |
} else { | |
setSelectedOptions([...selectedOptions, option]); | |
} | |
}; |
이렇게 간단히 가능하겠다!
const A = "웅!"; | ||
const C = "반반"; | ||
const B = "아니ㅠ"; |
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.
의미를 알아볼 수 있는 변수명으로 지정해주세용!
handleOptionChange("웅!"); | ||
}} | ||
> | ||
{" "} | ||
응!! | ||
</Question> | ||
<Question onClick={() => handleOptionChange("반반")}> 반반</Question> | ||
<Question onClick={() => handleOptionChange("아니ㅠ")}> |
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.
이미 상수로 선언해놨으니까 그거 변수명만 리팩토링해서 가져다쓰면 되겠다~!
data.answer.toString() === selectedOptions.toString() | ||
); | ||
|
||
return matchingAnimalData ? matchingAnimalData : "알 수 없음"; |
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.
return matchingAnimalData ? matchingAnimalData : "알 수 없음"; | |
return matchingAnimalData || "알 수 없음"; |
이렇게도 가능하답니당!
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.
헉 넘 조아욤
✨ 구현 기능 명세
🌱 기본 조건
🧩 기본 과제
[취향대로 추천]
답변 선택
이전으로, 다음으로(결과보기) 버튼
아무것도 선택되지 않았을 시 버튼을 비활성화 시킵니다.
→ 눌러도 아무 동작 X
→ 비활성화일 때 스타일을 다르게 처리합니다.
이전으로
버튼을 누르면 이전 단계로 이동합니다.다음으로
/결과보기
버튼을 누르면 다음 단계로 이동합니다.버튼 호버시 스타일 변화가 있습니다.
결과
[ 랜덤 추천 ]
[ 공통 ]
다시하기
버튼→ 랜덤추천이면
랜덤 추천 start
화면으로, 취향대로 추천이면취향대로 추천 start
화면으로 돌아갑니다.→ 모든 선택 기록은 리셋됩니다.
생각과제
💎 PR Point
자기선택자를 이용해서 호버시 배경색을 다르게 했다
index.css에 background-color 에 대한 css를 추가했다
원래는 버튼 active로 내가 jsx안에서 스타일링하려고 했는데, 여러 버튼이 중복해서 눌리면 안되는데 클릭을 한번 하면 안 풀린다는 점에서 조금 복잡해질 것 같다고 생각했고, 기본 스타일링으로 outline이 되어있는 걸 보고 "같은 로직으로 배경색도 바뀌게 하면 되지않을까?? " 해서 css를 뒤지다가 저걸 찾아서 밑에다가 추가하게 되었다..ㅎㅎ
이런식으로 페이지별로 함수를 만들어서 해당 페이지만 보일수 있게 설정하고(true) 나머지는 안보이게 설정했다(false)
ex) 만약 내가 페이지 1에 있다면. 이전 버튼을 누르면
goPage0
함수 호출 , 다음 버튼을 누르면goPage2
함수 호출showPage{n} 이 true 일 때 Page{n} 보이게. 이때 필요한 함수(다음이랑 이전 페이지로 이동하기 위한)는 props로 보내준다.
2. 그러고 유저가 페이지마다 대답을 선택할 때, 해당 대답이 배열로 저장이 되고, 최종적으로 animalData와 비교했을 때, 일치하는 answer의 동물이름과 사진을 가져올 수 있도록 구현했다.
2.1 이 배열에 유저가 선택한 대답을 저장해둔다.
const [selectedOptions, setSelectedOptions] = useState([]);
2.2 대답을 선택하면 해당 대답과 대응되면 option값을 저장한다.
<Question onClick={() => handleOptionChange("반반")}> 반반</Question>
2.3 그 option값을 goPage1의 매개변수로 전달해서 최종 배열에 추가해준다.
이렇게 3페이지를 거치면 길이가 3인 배열이 될 것이고, 그 배열을 animalData와 비교!!
🥺 소요 시간, 어려웠던 점
8h
경로가 달라서 이미지가 계속 로드가 안 되었다. 이미지를 public폴더에 넣어서 하니 해결되었다.
한번만 선택을 눌러도 비활성화가 풀려야 하는데 희한하게 두번 눌러야 그제서야 풀렸다.
useState 비동기적으로 상태를 업데이트하며, 이로 인해 클릭 이벤트 후 buttonActive가 즉시 false로 업데이트되지 않을 수 있다. 따라서 이를 해결하려면 setSelectedOption과 setButtonActive를 동기적으로 처리해야 한다.
위의 코드에서 setButtonActive(option !== null) 부분은 setSelectedOption이 완료된 후에 실행되므로, 상태 업데이트가 동기적으로 이루어진다!
🌈 구현 결과물
Screen.Recording.2023-11-10.at.8.03.20.PM.mov
Screen.Recording.2023-11-10.at.8.00.47.PM.mov