-
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주차 기본/심화/생각 과제] 점메추 🍚 #3
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.
코드맛집이라고 소문나서 구경왔다가 리뷰 딱 하나! 의견 남기고 갑니다 ㅎ ㅎ ㅎㅎ
진짜 코드 기가 막히게 깔끔하네요.. 주석도 적절히 사용해줘서 정말 이해하기 좋은 코드
그 자체인 것 같아요! 저도 수빈이 본받아서 주석 사용해봐야겠어용 😎
수고많았습니다 ~~ 👍 🥰
<Header restart={restart} /> | ||
<Section> | ||
<Quetion>오늘의 추천음식은</Quetion> | ||
<Answer> | ||
<WhiteBox> | ||
<FoodImg src={result} alt="추천음식"></FoodImg> | ||
</WhiteBox> | ||
</Answer> | ||
<Button type="button" onClick={() => retry(type)}> | ||
다시하기 | ||
</Button> | ||
<MainCharacter /> | ||
</Section> |
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.
pages 디렉토리 내에 들어있는 컴포넌트들을 보면, CountdownPage
를 제외하고 모두 아래와 같은 구조가 반복되고 있는 것 같아요!
<Header/>
<Section>
{조건에따라 <ProgressBar/>}
<Question>{질문}</Question>
<Answer>{선택지}</Answer>
{조건에따라 <Button>{버튼}</Button>}
<MainCharacter/>
</Section>
네가지 페이지에서 반복 사용되는 UI 컴포넌트를 분리한 것 아주 좋아요!! 👍🏻🥰
여기서 약간 다른 모듈화 방식도 한가지 제안 드리자면,
결국 위에 쓰인 N가지 컴포넌트들이 같은/거의 유사한 조합으로 반복 사용되고 있는거잖아요??
따라서 반복되는 그 조합을 하나의 레이아웃 컴포넌트
로 생성해서 반복 사용해주는 방법도 깰꼼할 것 같아서 이런 방식도 있다! 하고 의견 남기고 갑니당
즉,
- 모듈화 하지 않았다면
N개
의 컴포넌트를3-4번씩 반복 생성
했을 것을 - 현재 수빈이는 Header, MainCharacter, Question, Section 등의
N개
의 컴포넌트를1번만 생성
하여 재사용성을 높였고, - 제가 추가로 제안한 방식은 이들의 반복되는 조합을
1개
의 컴포넌트로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.
정성스런 리뷰에 감동 받았덩 🥹 리뷰 반영해서 이렇게 컴포넌트 하나로 빼서 리팩토링 해봤어용 🖤
const MainSection = () => {
return (
<>
{page === PAGE.TYPE_SELECT ? (
<Header isTypeSelectPage={true} />
) : (
<Header restart={restart} />
)}
<Section>
{}
{isQuestionPage && <ProgressBar page={page - 1} />}
<Quetion>{question}</Quetion>
<Answer>{contents}</Answer>
{mainCharacter}
{nav}
</Section>
</>
);
}
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.
갓기를 우아한형제들로.....~ 수고 많았어 수빈이!!! 내가 좀 더 많이 알았다면 이것저것 많이 써줄텐데 지금은 내가 수빈이한테 배워가야하는 수준이라..허허.. 고생했어!!!!
저는 보통 `UI 단위`로 나누기는 하는데, 컴포넌트가 너무 길어서 `가독성`이 떨어지거나 다른 컴포넌트에서 `재사용`될 수 있다면 | ||
컴포넌트로 분리하는 편이에요 🐝 |
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.
우리 갓기 멋져용~!
week3/assignment/점메추 🍚/index.html
Outdated
@@ -0,0 +1,16 @@ | |||
<!doctype html> | |||
<html lang="en"> |
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.
별건 아니지만 이거 ko로 바꿔줄까....?
const initialState = { | ||
page: 0, | ||
selectedRecommendType: "", | ||
selectedRegion: "", | ||
selectedAmount: "", | ||
selectedTaste: "", | ||
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.
이거 좀 좋은거 같다......... 분명 배웠는데 어떻게 쓰는지를 몰라서 못써봤는데 다음 리팩토링 할 때 한번 해볼게!
}; | ||
|
||
function App() { | ||
const [state, dispatch] = useReducer(reducer, initialState); |
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.
useReducer를 어떻게 쓰는지 감이 안잡혔었는데 수빈이 코드 보면서 조금은 익숙해진거같아 .. 고마워!
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.
나도! 거의 useState라도 배워가자란 생각으로 과제 했는데 useReducer에 대해서 한번 더 찾아보고 알아가게 된 계기가 된 것 같아
const prevPage = () => { | ||
dispatch({ type: "MOVE_TO_PREV_PAGE" }); | ||
}; |
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.
useReducer로 이렇게 주니까 완전 깔끔하다..
<DisabledNextButton type="button" disabled> | ||
다음으로 | ||
</DisabledNextButton> |
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.
progress bar까지 만든거 완전 최고다..... 시연영상 보는데 감탄했음ㅎㅎ
<Section> | ||
<LoadingBox> | ||
<CountNumber>{count}</CountNumber> | ||
<Spinner src={image} alt="회전하고 있는 음식들" /> |
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.
alt까지 야무지다..
nextPage, | ||
restart, | ||
}) => { | ||
const mainCharacter = useMemo(() => <MainCharacter />, []); |
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.
난 아직 useMemo가 어려워서 .. 아니 언제 어떻게 쓰이는지 감이 잡히질 않더라.. 그치만 사용한 수빈이 칭찬해~
<> | ||
<Header restart={restart} /> | ||
<Section> | ||
<ProgressBar page={currentPage - 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.
요기는 왜 currentPage -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.
이거 페이지가
export const PAGE = {
RECOMMEND_BY_TYPE_QUESTION_1: 2,
RECOMMEND_BY_TYPE_QUESTION_2: 3,
RECOMMEND_BY_TYPE_QUESTION_3: 4,
};
이렇게 설정돼있는데, progressbar 너비는
const Progress = styled.div`
width: ${({ page }) => page * 33.3}%;
`;
이렇게 계산돼서 -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.
수비니 넘넘 수고했어요!!! 상수로 빼는 것도 너무 좋구, 고민 많이 한 것 같아! 최고!
`; | ||
|
||
const Title = styled.h1` | ||
font-size: ${({ theme }) => theme.fontSize.md}; |
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.
그때 글로벌스타일 물어봤던 게 기억나서!
요기에 정리 잘 되어 이쏘
뺙 🐣
return ( | ||
<HeaderWrapper> | ||
<Title>오늘의 점메추</Title> | ||
{!isTypeSelectPage && <Button onClick={() => restart()}>처음으로</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.
아고 깔끔해!!!
import mainCharacter from "../assets/img/character.png"; | ||
|
||
export default function MainCharacter() { | ||
return <Character src={mainCharacter} alt="캐릭터" />; |
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.
마자요 흐흐 😝
import { BalloonText, RadioBalloon, RadioInput } from "../styles/GlobalStyle"; | ||
|
||
export const Option = ({ option, saveAnswer, currentPage, selectedValue }) => ( | ||
<div key={option.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.
요기 구조분해할당 써서 조금 더 깔끔하게 해도 좋지 않을까? 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.
와앙 고마워 🖤 리팩완료 🚀
export const Option = ({ option, saveAnswer, currentPage, selectedValue }) => {
const { id, name, label } = option;
return (
<div key={id}>
<RadioInput
type="radio"
id={id}
name={name}
onChange={() => saveAnswer(id, currentPage)}
checked={selectedValue === id}
></RadioInput>
<RadioBalloon htmlFor={id}>
<BalloonText>{label}</BalloonText>
</RadioBalloon>
</div>
);
};
background-color: ${({ theme }) => theme.colors.grey}; | ||
`; | ||
|
||
const Progress = styled.div` |
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 GlobalStyle; | ||
|
||
export const Section = styled.section` |
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.
요건 GlobalStyle 파일에 넣기보다는 따로 commonStyle 파일로 분리해서 정리하는 게 더 좋을 것 같다아!
display: flex; | ||
justify-content: center; | ||
align-items: center; |
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 뺄 수 있어요!
display: flex; | |
justify-content: center; | |
align-items: center; | |
${centerFlex} |
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.
꿀팁 너무 고맙습니당 🖤
amount: "properly", | ||
taste: "fat", | ||
score: 0, | ||
image: "src/assets/img/chicken-ribs.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.
요거 그냥 파일보다는 사진 주소로 넣는게 ㄷㅓ 편할거야!
}, | ||
}; | ||
|
||
export const regionOptions = [ |
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.
ㅎㅎㅎ 분리해야겠다!!
@@ -0,0 +1,209 @@ | |||
export const foodData = { |
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.
이거 객체가 아니라 리스트면 method 쓰기가 더 쉬울 것 같아!
case "MOVE_TO_PREV_PAGE": // 이전 페이지로 이동 | ||
return { ...state, page: state.page - 1 }; | ||
case "MOVE_TO_NEXT_PAGE": // 다음 페이지로 이동 | ||
return { ...state, page: state.page + 1 }; | ||
case "MOVE_TO_PAGE": // 특정 페이지로 이동 | ||
return { ...state, page: action.payload }; |
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 */ | ||
case PAGE.RECOMMEND_BY_TYPE_QUESTION_1: | ||
dispatch({ type: "MOVE_TO_NEXT_PAGE" }); | ||
break; | ||
|
||
/* 취향대로 - 2 */ | ||
case PAGE.RECOMMEND_BY_TYPE_QUESTION_2: | ||
dispatch({ type: "MOVE_TO_NEXT_PAGE" }); | ||
break; | ||
|
||
/* 취향대로 - 3 */ | ||
case PAGE.RECOMMEND_BY_TYPE_QUESTION_3: | ||
resultPage(); | ||
break; | ||
|
||
default: | ||
dispatch({ type: "MOVE_TO_NEXT_PAGE" }); | ||
break; |
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.
switch문 대박 잘 씀!
} | ||
}; | ||
|
||
/* 선택된 답변에 해당하는 음식의 점수를 증가 시킨 후 가장 점수가 높은 음식을 찾아 결과 화면으로 이동 */ |
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 maxCount = -1; | ||
|
||
for (const food in foodData) { | ||
if (foodData[food].score > maxCount) { | ||
bestFoods = [food]; |
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.
내가 아는 for문이랑 너무 달라.. ()안에 설명가능할까효..?
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.
for ... in
구문인데 foodData를 순회하는 거라고 생각하면 돼!!!
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/for...in
요고 한번 참고해봐!
/* 답변 초기화 */ | ||
const resetAnswer = () => { | ||
dispatch({ type: "SET_SELECTED_REGION", payload: "" }); | ||
dispatch({ type: "SET_SELECTED_AMOUNT", payload: "" }); | ||
dispatch({ type: "SET_SELECTED_TASTE", payload: "" }); | ||
dispatch({ type: "SET_RESULT", payload: "" }); | ||
Object.keys(foodData).forEach((food) => { | ||
foodData[food].score = 0; | ||
}); |
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.
마자용~!!
week3/assignment/점메추 🍚/src/App.jsx
Outdated
<TypeQuestionPage | ||
type={question.amount} | ||
options={amountOptions} | ||
selectedValue={state.selectedAmount} | ||
saveAnswer={saveAnswer} | ||
currentPage={state.page} | ||
prevPage={prevPage} | ||
nextPage={nextPage} | ||
restart={restart} | ||
/> |
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 짱많아.. 고생많았당!
<React.StrictMode> | ||
<ThemeProvider theme={theme}> | ||
<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.
다른 곳에서 theme로 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.
ThemeProvider를 적용하는 방법이에요!!
✨ 구현 기능 명세
🌱 기본 조건
🧩 기본 과제
[취향대로 추천]
답변 선택
이전으로, 다음으로(결과보기) 버튼
아무것도 선택되지 않았을 시 버튼을 비활성화 시킵니다.
→ 눌러도 아무 동작 X
→ 비활성화일 때 스타일을 다르게 처리합니다.
이전으로
버튼을 누르면 이전 단계로 이동합니다.다음으로
/결과보기
버튼을 누르면 다음 단계로 이동합니다.버튼 호버시 스타일 변화가 있습니다.
결과
[ 랜덤 추천 ]
[ 공통 ]
다시하기
버튼→ 랜덤추천이면
랜덤 추천 start
화면으로, 취향대로 추천이면취향대로 추천 start
화면으로 돌아갑니다.→ 모든 선택 기록은 리셋됩니다.
🌠 심화 과제
theme + Globalstyle 적용
애니메이션
헤더
처음으로
버튼→ 추천 종류 선택 화면일시 해당 버튼이 보이지 않습니다.
→ 처음 추천 종류 선택 화면으로 돌아갑니다.
→ 모든 선택 기록은 리셋됩니다.
[ 취향대로 추천 ]
단계 노출
이전으로 버튼
useReducer
,useMemo
,useCallback
을 사용하여 로직 및 성능을 최적화합니다.생각과제
💎 PR Point
저는 이번 과제에서
컴포넌트 분리
에 신경을 써봤습니다🧘🏻♀️ useReducer 를 사용하여 상태관리를 했어요
🧠 useMemo 를 사용하여 렌더링을 줄여보았아요
⏱️ useInterval 을 사용하여 setInterval 관련 이슈를 해결했어요
최적화를 위해 react dev tool을 사용하였는데 그러던 중 컴포넌트가 넘어갔는데도 setInterval이 계속 진행되고 있는 것을 발견했어요
저는 이글을 참고해서 수정해보았습니다
📂 크게 5개의 화면으로 구성 했어요
♻️ 재사용이 가능한 공통 컴포넌트를 분리 했어요
💅 여러 컴포넌트에서 사용하는 스타일은 Global Style 로 정의했어요
🎨 색상과 폰트 크기를 Theme Provider 로 관리했어요
👀 page 넘버 를 상수화 하여 가독성을 높였어요
🍚 취향대로 추천의 로직은 다음과 같습니다
선택한 답변에 해당하는 음식들에 점수를 더해서 가장 높은 점수들을 갖는 것 중 랜덤으로 결과를 구했어요
🥢 질문 별 아이템은 이렇게 나눴어요
질문 별 아이템 🍽️
🥺 소요 시간, 어려웠던 점
3d
🌈 구현 결과물
취향대로 추천
recommend_by_type.mov
랜덤 추천
recommand_by_random.mov
다음/이전 버튼, 처음으로
next_prev_test.mov