-
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주차 기본/심화 과제] 🍚 점메추 🍚 #7
Conversation
const handleStepBtn = (type: '이전으로' | '다음으로' | '결과보기' | '다시하기') => { | ||
switch (type) { | ||
case '이전으로': | ||
setStep(step - 1); | ||
break; | ||
case '다음으로': | ||
setStep(step + 1); | ||
break; | ||
case '결과보기': | ||
setStep(4); | ||
setSelectedMenu(selectMenu(input)); | ||
break; | ||
case '다시하기': | ||
setStep(0); | ||
setInput({ | ||
recommendType: input.recommendType, | ||
country: undefined, | ||
ingredients: undefined, | ||
broth: undefined, | ||
}); | ||
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문 이용해서 구현하니깐 엄청 깔끔하고 좋은 것 같아요!! 😍
const FunnelLayout = () => { | ||
const [step, setStep] = useState<stepState>(0); | ||
const [recommendType, setRecommendType] = useState<recommendTypeState>(undefined); | ||
|
||
return ( | ||
<> | ||
<FunnelLayoutWrapper> | ||
<FunnelTitle step={step} /> | ||
<FunnelBody step={step} recommendType={recommendType}></FunnelBody> | ||
</FunnelLayoutWrapper> | ||
</> | ||
); | ||
}; | ||
|
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.
네..먹겠습니...ヲヲヲヲ너무 단호박🎃
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문으로 빼주는 것도 좋겠당
const convertInputType: { [key: string]: string } = { | ||
한식: 'K', | ||
일식: 'J', | ||
중식: 'C', | ||
밥: 'R', | ||
면: 'N', | ||
'고기/해물': 'M', | ||
국물시러: 'X', | ||
국물조아: 'O', | ||
}; | ||
|
||
export const convertStringToName: { [key: string]: string } = { | ||
KRX: '비빔밥', | ||
KRO: '국밥', | ||
KNX: '비빔냉면', | ||
KNO: '칼국수', | ||
KMX: '삼겹살', | ||
KMO: '꽃게탕', | ||
JRX: '텐동', | ||
JRO: '명란오차즈케', | ||
JNX: '마제소바', | ||
JNO: '라멘', | ||
JMX: '돈카츠', | ||
JMO: '모츠나베', | ||
CRX: '새우볶음밥', | ||
CRO: '해물누룽지탕', | ||
CNX: '짜장면', | ||
CNO: '짬뽕', | ||
CMX: '깐풍기', | ||
CMO: '마라탕', | ||
}; | ||
|
||
export const selectMenu = (input: inputState) => { | ||
let finalString = ''; | ||
|
||
switch (input.recommendType) { | ||
case '취향': | ||
if (input.country && input.ingredients && input.broth) { | ||
finalString += convertInputType[input.country]; | ||
finalString += convertInputType[input.ingredients]; | ||
finalString += convertInputType[input.broth]; | ||
return finalString; | ||
} | ||
break; | ||
case '랜덤': | ||
finalString += ['K', 'J', 'C'][Math.floor(Math.random() * 3)]; | ||
finalString += ['R', 'N', 'M'][Math.floor(Math.random() * 3)]; | ||
finalString += ['O', 'X'][Math.floor(Math.random() * 2)]; | ||
return finalString; | ||
default: | ||
return finalString; | ||
} | ||
}; |
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에 이 부분 고민 많이 하셨다고 하셔서 제가 구현한 방법 한 번 적어볼게용!
저는 처음에 먼저 객체를 만들고 그 객체에 선택값을 넣어줬습니다! 그리고 결과 보여줄 때는 선택값에 맞는 메뉴만 필터링해주었어요!
const [options, setOption] = useState({
country: '',
main: '',
soup: '',
});
const menu = MENU_LIST.filter((item) => {
return (
item.country === props.options.country && item.main === props.options.main && item.soup === props.options.soup
);
});
|
||
const fonts = { | ||
Title1: css` | ||
font-family: 'DOSPilgiMedium'; |
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: ( | ||
<> | ||
<SelectBtnsWrapper> | ||
<SelectBtn | ||
onClick={() => handleCountry('한식')} | ||
type={input.country === '한식' ? 'selected' : undefined}> | ||
한식 | ||
</SelectBtn> | ||
<SelectBtn | ||
onClick={() => handleCountry('일식')} | ||
type={input.country === '일식' ? 'selected' : undefined}> | ||
일식 | ||
</SelectBtn> | ||
<SelectBtn | ||
onClick={() => handleCountry('중식')} | ||
type={input.country === '중식' ? 'selected' : undefined}> | ||
중식 | ||
</SelectBtn> | ||
</SelectBtnsWrapper> | ||
<StepBtnsWrapper> | ||
<StepBtn onClick={() => handleStepBtn('이전으로')}>이전으로</StepBtn> | ||
<StepBtn onClick={() => handleStepBtn('다음으로')} type={input.country || 'disabled'}> | ||
다음으로 | ||
</StepBtn> | ||
</StepBtnsWrapper> | ||
</> | ||
), | ||
2: ( | ||
<> | ||
<SelectBtnsWrapper> | ||
<SelectBtn | ||
onClick={() => handleIngredients('밥')} | ||
type={input.ingredients === '밥' ? 'selected' : undefined}> | ||
밥 | ||
</SelectBtn> | ||
<SelectBtn | ||
onClick={() => handleIngredients('면')} | ||
type={input.ingredients === '면' ? 'selected' : undefined}> | ||
면 | ||
</SelectBtn> | ||
<SelectBtn | ||
onClick={() => handleIngredients('고기/해물')} | ||
type={input.ingredients === '고기/해물' ? 'selected' : undefined}> | ||
고기/해물 | ||
</SelectBtn> | ||
</SelectBtnsWrapper> | ||
<StepBtnsWrapper> | ||
<StepBtn onClick={() => handleStepBtn('이전으로')}>이전으로</StepBtn> | ||
<StepBtn onClick={() => handleStepBtn('다음으로')} type={input.ingredients || 'disabled'}> | ||
다음으로 | ||
</StepBtn> | ||
</StepBtnsWrapper> | ||
</> | ||
), | ||
3: ( | ||
<> | ||
<SelectBtnsWrapper> | ||
<SelectBtn | ||
onClick={() => handleBroth('국물시러')} | ||
type={input.broth === '국물시러' ? 'selected' : undefined}> | ||
국물 시러 | ||
</SelectBtn> | ||
<SelectBtn | ||
onClick={() => handleBroth('국물조아')} | ||
type={input.broth === '국물조아' ? 'selected' : undefined}> | ||
국물 조아 | ||
</SelectBtn> | ||
</SelectBtnsWrapper> | ||
<StepBtnsWrapper> | ||
<StepBtn onClick={() => handleStepBtn('이전으로')}>이전으로</StepBtn> | ||
<StepBtn | ||
onClick={() => handleStepBtn('결과보기')} | ||
type={input.broth === undefined ? 'disabled' : undefined}> | ||
결과보기 | ||
</StepBtn> | ||
</StepBtnsWrapper> | ||
</> | ||
), | ||
4: ( | ||
<> | ||
{count > 0 && input.recommendType === '랜덤' ? ( | ||
<CountDown count={count} setCount={setCount} /> | ||
) : ( | ||
selectedMenu && ( | ||
<ResultWrapper> | ||
<ImgWrapper src={`${selectedMenu}.jpeg`} alt={convertStringToName[selectedMenu]} /> | ||
<SelectedMenuWrapper>✱{convertStringToName[selectedMenu]}✱</SelectedMenuWrapper> | ||
<StepBtn onClick={() => handleStepBtn('다시하기')}>다시하기</StepBtn> | ||
</ResultWrapper> | ||
) | ||
)} | ||
</> | ||
), | ||
}[step] | ||
} | ||
</> | ||
); | ||
}; |
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.
아 근데 depth 때문에 한 파일에 넣은 거였군요..ㅜ
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.
저는 분리하는거 찬성합니다! 뎁스가 그렇게 치명적이지도 않고, 특히 step 1,2,3은 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.
코드 너무 깔끔하고 특히 funnel 구조는 처음 알게되었는데 설명도 잘 써주셔서 많이 배워갑니당! 너무 고생하셨어요 🥰 심화까지 갓벽. 제가 타스 무지랭이라 코드를 완벽하게 이해하지는 못했지만 열심히 리뷰 남겼보았습니다..ㅎ 그리고 저도 컴포넌트 분리할 때 가독성이랑 depth 때문에 고민 많이 했었는데 너무 어려운 것 같아요..저희 같이 고민해봐요ㅜㅜ 이번 과제도 홧팅!
? '국물 있는거, 없는거?' | ||
: '넌 오늘 이걸 먹어라'} |
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문으로 빼주는 것도 좋겠당
? '국물 있는거, 없는거?' | ||
: '넌 오늘 이걸 먹어라'} | ||
</TitleWrapper> | ||
<StepWrapper>{input.recommendType === '취향' && step > 0 && step < 4 && step + ' / 3'}</StepWrapper> |
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.
<StepWrapper>{input.recommendType === '취향' && step > 0 && step < 4 && step + ' / 3'}</StepWrapper> | |
{input.recommendType === '취향' && step > 0 && step < 4 && <StepWrapper>{`${step} / 3`}</StepWrapper>} |
렌더링 자체를 결정할 수 있도록 이렇게는 어떤가용?
</> | ||
), | ||
1: ( | ||
<> | ||
<SelectBtnsWrapper> | ||
<SelectBtn | ||
onClick={() => handleCountry('한식')} | ||
type={input.country === '한식' ? 'selected' : undefined}> | ||
한식 | ||
</SelectBtn> | ||
<SelectBtn | ||
onClick={() => handleCountry('일식')} | ||
type={input.country === '일식' ? 'selected' : undefined}> | ||
일식 | ||
</SelectBtn> | ||
<SelectBtn | ||
onClick={() => handleCountry('중식')} | ||
type={input.country === '중식' ? 'selected' : undefined}> | ||
중식 | ||
</SelectBtn> | ||
</SelectBtnsWrapper> | ||
<StepBtnsWrapper> | ||
<StepBtn onClick={() => handleStepBtn('이전으로')}>이전으로</StepBtn> | ||
<StepBtn onClick={() => handleStepBtn('다음으로')} type={input.country || 'disabled'}> | ||
다음으로 | ||
</StepBtn> | ||
</StepBtnsWrapper> | ||
</> | ||
), | ||
2: ( | ||
<> | ||
<SelectBtnsWrapper> | ||
<SelectBtn | ||
onClick={() => handleIngredients('밥')} | ||
type={input.ingredients === '밥' ? 'selected' : undefined}> | ||
밥 | ||
</SelectBtn> | ||
<SelectBtn | ||
onClick={() => handleIngredients('면')} | ||
type={input.ingredients === '면' ? 'selected' : undefined}> | ||
면 | ||
</SelectBtn> | ||
<SelectBtn | ||
onClick={() => handleIngredients('고기/해물')} | ||
type={input.ingredients === '고기/해물' ? 'selected' : undefined}> | ||
고기/해물 | ||
</SelectBtn> | ||
</SelectBtnsWrapper> | ||
<StepBtnsWrapper> | ||
<StepBtn onClick={() => handleStepBtn('이전으로')}>이전으로</StepBtn> | ||
<StepBtn onClick={() => handleStepBtn('다음으로')} type={input.ingredients || 'disabled'}> | ||
다음으로 | ||
</StepBtn> | ||
</StepBtnsWrapper> | ||
</> | ||
), | ||
3: ( | ||
<> | ||
<SelectBtnsWrapper> | ||
<SelectBtn | ||
onClick={() => handleBroth('국물시러')} | ||
type={input.broth === '국물시러' ? 'selected' : undefined}> | ||
국물 시러 | ||
</SelectBtn> | ||
<SelectBtn | ||
onClick={() => handleBroth('국물조아')} | ||
type={input.broth === '국물조아' ? 'selected' : undefined}> | ||
국물 조아 | ||
</SelectBtn> | ||
</SelectBtnsWrapper> | ||
<StepBtnsWrapper> | ||
<StepBtn onClick={() => handleStepBtn('이전으로')}>이전으로</StepBtn> | ||
<StepBtn | ||
onClick={() => handleStepBtn('결과보기')} | ||
type={input.broth === undefined ? 'disabled' : undefined}> | ||
결과보기 | ||
</StepBtn> | ||
</StepBtnsWrapper> | ||
</> | ||
), | ||
4: ( | ||
<> | ||
{count > 0 && input.recommendType === '랜덤' ? ( | ||
<CountDown count={count} setCount={setCount} /> | ||
) : ( | ||
selectedMenu && ( | ||
<ResultWrapper> | ||
<ImgWrapper src={`${selectedMenu}.jpeg`} alt={convertStringToName[selectedMenu]} /> | ||
<SelectedMenuWrapper>✱{convertStringToName[selectedMenu]}✱</SelectedMenuWrapper> | ||
<StepBtn onClick={() => handleStepBtn('다시하기')}>다시하기</StepBtn> | ||
</ResultWrapper> | ||
) | ||
)} | ||
</> | ||
), | ||
}[step] | ||
} | ||
</> | ||
); | ||
}; |
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.
저는 분리하는거 찬성합니다! 뎁스가 그렇게 치명적이지도 않고, 특히 step 1,2,3은 UI와 로직 구조가 반복되어서 공통 컴포넌트로 분리하면 너무 좋을 것 같아요!
배포링크
✨ 구현 기능 명세
🌱 기본 조건
🧩 기본 과제
[취향대로 추천]
답변 선택
이전으로, 다음으로(결과보기) 버튼
아무것도 선택되지 않았을 시 버튼을 비활성화 시킵니다.
→ 눌러도 아무 동작 X
→ 비활성화일 때 스타일을 다르게 처리합니다.
이전으로
버튼을 누르면 이전 단계로 이동합니다.다음으로
/결과보기
버튼을 누르면 다음 단계로 이동합니다.버튼 호버시 스타일 변화가 있습니다.
결과
[ 랜덤 추천 ]
[ 공통 ]
다시하기
버튼→ 랜덤추천이면
랜덤 추천 start
화면으로, 취향대로 추천이면취향대로 추천 start
화면으로 돌아갑니다.→ 모든 선택 기록은 리셋됩니다.
🌠 심화 과제
theme + Globalstyle 적용
애니메이션
헤더
처음으로
버튼→ 추천 종류 선택 화면일시 해당 버튼이 보이지 않습니다.
→ 처음 추천 종류 선택 화면으로 돌아갑니다.
→ 모든 선택 기록은 리셋됩니다.
[ 취향대로 추천 ]
단계 노출
이전으로 버튼
useReducer
,useMemo
,useCallback
을 사용하여 로직 및 성능을 최적화합니다.💎 PR Point
funnel 구조 사용
FunnelBody에서는 각 step별 제목을 제외한 모든 컴포넌트(버튼들)를 렌더링합니다. prop으로 넘겨받은 step에 따라서 각 step에 해당하는 컴포넌트를 조건부로 렌더링하는데, 이 각각의 단계들을 다른 파일로 (First.tsx, Second.tsx ... 등) 분리할까 하다가, 괜히 depth만 깊어지는 것 같아서 그냥 한 파일에 모두 넣어버렸습니다. 가독성과 depth 줄이기 중에 어떤 걸 선택해야 좋은 걸까요?
공통 컴포넌트
선택지에 따른 알맞는 결과물 도출 로직
이 부분이 고민이 많이 된 것 같습니다. 좀 요상한 방법인 것 같긴 한데, 저는 마치 MBTI 유형을 계산하듯이, 선택지 각각을 하나의 알파벳으로 정의하고 사용자의 선택을 string으로 이어붙여서 하나의 유형(?)을 만들었습니다. 그리고 이 일련의 문자열에 해당하는 이미지와 이에 매칭하는 음식 이름을 가져옴으로써 결과를 도출했습니다.
🥺 소요 시간, 어려웠던 점
12h
처음으로
버튼을 눌러도 CountDown이 초기화되지 않는 문제가 있었는데, setCountDown의 위치를 옮기고처음으로
버튼을 눌렀을 때 setCountDown을 사용하여 이를 3으로 초기화함으로써 해결했습니다.🌈 구현 결과물
배포링크