-
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주차 기본, 심화 과제 ] PICK YOUR BLACKUP #9
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.
일단 승희 코리에 감동 받아서 보은을 하러 온 건데ㅜㅜ 나만 너무 배우고 질문만 남기고 가는 것 같다는 생각이 드네요(⌒_⌒;)
일단 정성스런 PR 보고 너무나 깜짝 놀랐습니다!!! 상태 관리랑 flow에 관한 부분이 너무 자세하고 친절하게 써져 있어서 코드가 술술 읽혔습니다👍👍👍 진짜 짱짱짱이에요. 특히 step을 어떻게 하면 효율적이고 직관적으로 나눌 수 있을지 고민한 흔적이 많이 느껴졌습니다!! 저도 같은 고민을 했었는데 확실히 OB의 해결 방법을 들으니까 앞으로 어떻게 하면 좋을지,,,라는 감이 아주 살짜쿵 생긴 느낌?ㅎㅎ 확실히 체계적으로 상태를 관리하니까 서현이가 내준 심화 과제도 뚝딱뚝딱 해냈을 것 같다는 생각이 들었습니다!! 저도 앞으로 제가 어떻게 하다가 이런 코드를 짜게 됐는지 고민하는 과정을 PR에 담아보면 좋을 것 같다는 생각이 드네요(❁´◡`❁)
useReducer
로 최적화한 부분도 너무 인상 깊었습니다❤ 저는 여러 state들을 동시에 다루는 데에 집중해서 사용했는데 승희처럼 한 state들을 여러 케이스에 따라 효율적으로 관리할 수 있는 방법도 있다는 걸 배워갈 수 있어서 너무 좋았어요!
아무래도 제가 리액트가 처음이라 다른 분들 코드를 보며 제가 놓친 부분을 챙겨보려고 했는데 승희가 달아준 코리가 정말정말 큰 도움이 됐습니다💛 제 질문과 코드에 정성스런 답변 남겨줘서 너무너무너무 고마워요~!!~!~❤❤❤ (그리고 건강은 꼭 챙기길!!!!👊)
그리고 승희 뭔가 블랙업 재질 ㅋㅋ ㅋ
return ( | ||
<> | ||
<Wrapper> | ||
<Header |
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
를 여러 개 넘겨주는 건 아무 상관이 없나요??
예를 들어, useState
를 남발하는 건 성능 최적화에 좋지 않잖아요! 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.
props를 여러개 넘겨주는건 성능에 큰 상관은 없지만, 다만 보통의 경우 넘겨주는 props의 개수가 많아질 경우 그 props를 받아서 사용하는 컴포넌트 내부의 로직이 복잡할 때가 많다...는 점이 있는데요!
제가 최근에 컴포넌트 합성과 분리에 대해 공부하면서 내린 결론은, 이 부분은 개발자의 성향에 따라 아래의 두가지로 나뉘어 선택할 수 있을 것 같아요!
- 한번에 props가 많이 전달되지 않도록 컴포넌트를 잘게 쪼개서 설계한다.
- 컴포넌트를 과도하게 잘게 쪼개기 보다 최소한의 컴포넌트를 생성하고 달라지는 데이터는 props들을 넘겨주어 처리한다
1번의 경우
하나의 컴포넌트 내부 로직이 복잡하지 않지만 컴포넌트가 과도하게 많이 생성된다.
2번의 경우
최소한의 컴포넌트로 최대의 재사용성을 뽑을 수 있지만 props를 활용하는 내부 로직이 복잡해지는 경우가 많다.
|
||
export default Header; | ||
|
||
const HeaderWrapper = styled.header` |
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.
네! 지난기수에서도 스타일드 컴포넌트를 별도의 파일로 관리하는 경우는 한번도..본적 없는것 같아요!
스타일드 컴포넌트는 말그대로 해당 파일, 해당 컴포넌트의 스타일링을 위해 생성해주는 친구이기 때문에 그 특정 컴포넌트의 UI 렌더링을 담당하는 파일 내에 함께 위치해주는 것이 적합하기 떄문에 대부분 이런 형태를 취하는 것 같습니다.
스타일드 컴포넌트를 별도의 파일로 관리하게 되면 오히려 불필요한 import문이 많아지는거라고 생각해요!!
import reset from "styled-reset"; | ||
|
||
export const GlobalStyle = createGlobalStyle` | ||
${reset} |
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.
아유 깔꼼해라
상태 설계를 구체적으로 하고 시작했다는 부분이 코드에서 정말 잘 보였던 것 같아요 !! 저의 뒤죽박죽 prop 전달과 state 생성을 이렇게 깔꼼하게 처리할 수 있는 거였다니 .. 역시나 많이 배워갑니다아 !
수고했어요옹 !! 💗
import Header from "./components/Header"; | ||
import Onboarding from "./components/Onboarding"; | ||
import styled from "styled-components"; | ||
import PickMenu from "./components/PickMenu"; | ||
import ResultMenu from "./components/ResultMenu"; | ||
|
||
import { useReducer, useState } from "react"; | ||
import RandomMenu from "./components/RandomMenu"; |
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해주는게 좋을 것 같아요 ! (는 나도 늘 생각만 하고 못함)
const ContentWrapper = styled.main` | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
justify-content: center; | ||
|
||
position: relative; | ||
width: 60%; | ||
height: 70%; | ||
padding: 5rem; | ||
|
||
background-color: ${({ theme }) => theme.colors.gray}; | ||
color: ${({ theme }) => theme.colors.white}; | ||
border-radius: 1rem; | ||
|
||
box-shadow: 0 0 1rem ${({ theme }) => theme.colors.white}; | ||
`; |
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 순서가 지켜져 있는게 너무 깔끔하고 보기 좋아서 나도 이번 과제에 적용해보려 했는데 ! 얼레벌레 적용한 감이 있어서 (어떤 곳은 해두고 어떤 곳은 안 해두고 ㅋㅋ ㅜ) 아쉬웠는데 ..
승희 코드 보면서 다시한 번 습관을 들여야겠다 생각하고 갑니당
& > button { | ||
width: 5rem; | ||
height: 1.5rem; | ||
|
||
border-radius: 0.5rem; | ||
border: 0; | ||
|
||
background-color: ${({ theme }) => theme.colors.white}; | ||
|
||
&:hover { | ||
border: 0.2rem solid ${({ theme }) => theme.colors.black}; | ||
} | ||
&:disabled { | ||
border: 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.
헉 난 button wrapper 따로, button 따로 스타일링 지정 했는데 생각해보니 이렇게 wrapper에서 한 번에 버튼까지 스타일을 먹여줄 수 있겠구나 ? 메모메모
const ResultMenu = ({ choice, setChoice, dispatch }) => { | ||
const RESULT = DATA.find( | ||
(el) => | ||
el.category === choice[0] && | ||
el.season === choice[1] && | ||
el.color === choice[2] | ||
); |
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 state를 관리하는 useReducer | ||
const reducer = (step, action) => { | ||
switch (action.type) { | ||
case "GO_BACK": | ||
return -1; | ||
case "START_PICK": | ||
return 0; | ||
case "START_RANDOM": | ||
return 4; | ||
case "PREV": | ||
return step - 1; | ||
case "NEXT": | ||
return step + 1; | ||
case "GO_RESULT": | ||
return 3; | ||
} | ||
}; | ||
|
||
const [step, dispatch] = useReducer(reducer, -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.
승희 PR 읽고 제 의견을 살짝 첨부해봅니당 !
useState
와 useReducer
모두 상태값을 업데이트할 수 있는 Hook이여서 어느 경우에 선택하여 사용해야 하는 것인지 저도 고민을 많이했었는데 .. 저는 string, num과 같이 원시값을 초기값으로 가지고 로직에 따라 업데이트하는 경우에는 useState
를 사용하고, 배열이나 객체와 같이 여러개의 필드들이 연관되어 업데이트 되어야하는 참조값을 초기값으로 가지는 경우나 여러개의 prop들이 연동되어 작동하는 경우에는 useReducer
를 사용하는 것이 좋다고 생각합니다 !_!
승희가 PR에 남겨준 것 처럼 각 숫자가 의미하는 컴포넌트를 코드를 보고 쉽게 파악하기는 어렵다.
는 점을 보완하기 위해서 useReducer
로 리팩토링했다는 점을 PR을 읽을 때는 와닿지 않았는데, 코드리뷰를 하면서 RANDOM을 선택할 경우 step 이 4로 지정되는 부분을 보면서 승희가 무엇을 보완하고 싶었는지랑 useReducer
을 왜 쓰게 되었는지 더 잘 이해할 수 있었고 고민과 공부의 흔적을 많이 볼 수 있었던 것 같아요!
저는 개인적으로 ! 승희가 구현한 로직에 따르면 기존에 구현했던 것 처럼 useState
를 써서 구현하는 것도 괜찮을 것 같아요 ! 사실 step 하나만을 두고(useState 하나만으로 !) 전체 로직 flow를 처리할 수 있다는게 저는 오히려 더 간결하고 깔끔하게 로직을 구현한 것 같아서 감탄했답니다.
잘 정리된 아티클 하나 남기고 갑니다 ! 흐흐
useState VS 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.
진짜 유용한 정보...감사합니다💛
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 작성순서까지 하나하나 배울 점들이 정말 많은 코드를 보는 기분입니다..🥹 특히나 이번주는 프로젝트 초기에 객체 구조를 설계하는 방식이 이후 코드 작성을 훨씬 간편하게 만들어줄 수 있구나를 많이 배울 수 있었던 주였던 것 같아요.. 언니와 함께하는 잔디여서 너무 좋아요🍀...
& > button { | ||
width: 5rem; | ||
height: 1.5rem; | ||
|
||
border-radius: 0.5rem; | ||
border: 0; | ||
|
||
background-color: ${({ theme }) => theme.colors.white}; | ||
|
||
&:hover { | ||
border: 0.2rem solid ${({ theme }) => theme.colors.black}; | ||
} | ||
&:disabled { | ||
border: 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.
이렇게 만들면 따로 Wrapper, Button 컴포넌트를 따로 만들지 않고도 내부에 있는 요소에 스타일을 적용할 수 있군요🥹
이번 과제하면서 컴포넌트 임포트가 이렇게 길어져도 될까 고민을 정말 많이 했는데, 이런방식으로 파일의 갯수를 줄이는 방식도 있구나 배워갑니다!
const fonts = { | ||
kor: "font-family: 'Do Hyeon', sans-serif;", | ||
eng: "font-family: 'Bebas Neue', sans-serif;", | ||
}; |
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.
리액트로 섞어짜기하는 사람은 언니밖에 없을꺼야...디자이너 아니냐구요...
{CHOICE[step].map((el, idx) => ( | ||
<PickChoice | ||
key={el} | ||
onClick={() => { | ||
const newChoice = [...choice]; | ||
newChoice[step] = idx; | ||
setChoice(newChoice); | ||
}} | ||
$clicked={choice[step] === idx}> | ||
{el} | ||
</PickChoice> |
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,2,3 단계를 모두 적어주지 않아도 map 사용해서 선택방식을 순회해서 렌더시키면 한번만 적어줘도 되구나 배워가요!
코드보면서 완벽하게 이해하지 못했던 부분이 있어서 질문 하나 남겨요!
new choice를 정의하고 해당 값에 이전 choice를 복사해서 사용하는데 이전에 선택했던 값을 얕게 복사해서 배열에 추가한 걸로 이해하면 될까용??
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.
완전 좋은질문이예요!!!!! 스프레드연산자
를 사용한 얕은복사
맞습니당!!
state가 일반적인 원시값일 경우엔 우리가 setState(새 값)
으로 바로 state 값을 수정해줄 수 있는데요!
state가 객체, 혹은 배열의 형태일 경우, 특정 인덱스/일부를 업데이트하고자 할때 setState로 한번에 수정하는 것이 불가합니다!!
쉽게 예시를 들어 설명해볼게요!!
const [name, setName] = useState("");
setName("숭"); // -> OK
setName("늉"); // -> OK
const [choice, setChoice] = useState([]);
setChoice(['바나나', '사과']); // -> OK
// choice[0] 값만 '딸기'로 바꾸고 싶다면....?? 어케?
쉽게 말해, setState
함수로는 배열/객체의 전체를 업데이트해주는 방법 밖에 없고,
따라서 임시배열/객체를 만들어서, 원하는 특정 요소의 값을 수정해준 뒤,
수정된 임시배열/객체 전체를 다시 setState
로 반영시켜주는거죠!
const [choice, setChoice] = useState([]);
setChoice(['바나나', '사과']);
// choice[0] 값만 '딸기'로 바꾸고 싶다면....?? 어케?
const tempChoice = [...choice]; // choice 배열을 tempChoice(임시배열)에 얕은 복사
tempChoice[0] = '딸기'; // tempChoice 형태 : ['딸기', '사과']
setChoice(tempChoice); // choice 형태 : ['딸기', '사과'] -> 목적 달성!
const Buttons = ({ children }) => { | ||
return <ButtonsWrapper>{children}</ButtonsWrapper>; | ||
}; |
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.
이렇게 만들면 시작하기와 이전으로 다음으로 wrapper를 별도를 분리해서 만들지 않아도 되군요!
승희언니 파일 구조를 보면서 어떻게 저렇게 컴포넌트 갯수를 적게 유지하면서 코드를 짰는지 궁금했는지 이런방식으로 줄일수도 있구나를 코드리뷰하면서 진짜 많이 깨닫게 되네요...🥹
const ResultMenu = ({ choice, setChoice, dispatch }) => { | ||
const RESULT = DATA.find( | ||
(el) => | ||
el.category === choice[0] && | ||
el.season === choice[1] && | ||
el.color === choice[2] | ||
); |
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.
진짜 깔끔한 코드 진짜로...대박.......천잰가.................
처음 객체 구조를 보면서 신기하다고 생각했는데 마지막 도출 과정에서 이렇게 사용하기 위해서
category, season, color에 값을 부여했군요... 너무 깔끔하네요..
<OnboardingStartBtn | ||
onClick={() => { | ||
howPick === "취향대로 추천" | ||
? dispatch({ type: "START_PICK" }) | ||
: dispatch({ type: "START_RANDOM" }); |
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.
Hook이 생각보다 정말 어려워서 어떤 방식으로 적어야하는지 감을 잘 못잡겠다고 생각했었는데,
승희언니의 코드를 직접 읽으니까 더 잘 이해가 되는 것 같아요!
심지어 단계에도 1,2,3 아니라 직접 이름을 적어주니까 직관적이고, action 단위에 type을 지정하는 방식으로도 사용해서
이전으로 다음으로 버튼에도 적용할 수 있다는 점이 진짜 인상적이네용
// 선택 단계 step state를 관리하는 useReducer | ||
const reducer = (step, action) => { | ||
switch (action.type) { | ||
case "GO_BACK": | ||
return -1; | ||
case "START_PICK": | ||
return 0; | ||
case "START_RANDOM": | ||
return 4; | ||
case "PREV": | ||
return step - 1; | ||
case "NEXT": | ||
return step + 1; | ||
case "GO_RESULT": | ||
return 3; | ||
} | ||
}; | ||
|
||
const [step, dispatch] = useReducer(reducer, -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.
진짜 유용한 정보...감사합니다💛
✨ 구현 기능 명세
🌱 기본 조건
🧩 기본 과제
[취향대로 추천]
답변 선택
이전으로, 다음으로(결과보기) 버튼
→ 눌러도 아무 동작 X
→ 비활성화일 때 스타일을 다르게 처리합니다.
이전으로
버튼을 누르면 이전 단계로 이동합니다.다음으로
/결과보기
버튼을 누르면 다음 단계로 이동합니다.결과
[ 랜덤 추천 ]
[ 공통 ]
다시하기
버튼→ 랜덤추천이면
랜덤 추천 start
화면으로, 취향대로 추천이면취향대로 추천 start
화면으로 돌아갑니다.→ 모든 선택 기록은 리셋됩니다.
🌠 심화 과제
theme + Globalstyle 적용
애니메이션
헤더
처음으로
버튼→ 추천 종류 선택 화면일시 해당 버튼이 보이지 않습니다.
→ 처음 추천 종류 선택 화면으로 돌아갑니다.
→ 모든 선택 기록은 리셋됩니다.
[ 취향대로 추천 ]
단계 노출
이전으로 버튼
useReducer
,useMemo
,useCallback
을 사용하여 로직 및 성능을 최적화합니다.💎 PR Point
컴포넌트를 다음과 같이 분리하였다.
App.jsx
형태( 가독성을 위해 props 는 코드에서 삭제 )
Header
: 모든 flow와 단계에 걸쳐 동일하게 유지되는 페이지 최상단 헤더Onboarding
: 추천 종류 선택 단계 내부 UIPickMenu
: 취향대로 추천 flow 내부 UIRandomMenu
: 랜덤 추천 flow 내부 UIResultMenu
: 결과 화면 UILayout 디렉토리
Title
: 모든 flow, 단계에서 중복 사용되는 내부 헤더Button
: 모든 단계에서 중복 사용되는 내부 버튼이번 과제는 하나의 페이지에 컴포넌트만 바꿔가며 렌더링함을 통해 여러 단계, 여러 flow를 구현해야 했다.
따라서 가장 중요시해야 하는 부분은 여러 컴포넌트를 오고갈 상태(state) 라고 생각했고,
과제의 모든 flow와 단계를 하나의 state로 관리했다.
이렇게 모든 단계와 flow를 하나의 state로 관리하니,
과제에서 요구했던
위와 같은 조건을 매우 편리하게 충족시킬 수 있어서 좋았다.
그러나! 단계 뿐만 아니라 모든 flow와 결과 페이지 이동 등도 하나의 state로 관리하다 보니,
setState 사용의 목적이 코드에서 직관적으로 보이지 않는다는 한계점이 있었다.
이러한 한계점을 다음 내용인
useReducer로 최적화
를 통해 개선할 수 있었다.🌱 관련커밋 : feat: setStep을 useReducer로 최적화하기
📚 참고문헌 : useReducer 공식문서
현재 step state에 부여되는 액션은 다음과 같다
처음으로
버튼 클릭 시 setStep(-1) → 최초 단계로 돌아가기Start
버튼 클릭 시 howPick 값이 “취향대로 추천”이냐 “랜덤 추천”이냐에 따라이전으로
버튼 클릭 시 setStep(step-1), 다음으로 버튼 클릭 시 setStep(step+1)이렇게 다양한 목적으로 사용됨! 정리하면 Action은
setStep(-1)
setStep(0)
setStep(4)
setStep(step-1)
setStep(step+1)
setStep(3)
이번 프로젝트는 single page인 만큼
컴포넌트 간의 이동 로직이 불필요하게 복잡해지지 않게 구현하고자 이렇게 state 하나로 모든 이동을 구현하였지만, 이러한 방법은 직관성에 다소 치명적이다.
action에서 볼 수 있듯 일관성 있는 step이 아님에도 불구하고 -1부터 4까지의 숫자로 모두 나타냈기 때문에, 각 숫자가 의미하는 컴포넌트를 코드를 보고 쉽게 파악하기는 어렵다.
따라서 이러한 경우, 직관성을 높이기 위해 각 index가 가리키는 바를 상수 값으로 지정해줄 수 있다.
그러나 이럴 경우,
이전 스텝으로 이동
,다음 스텝으로 이동
하는 액션까지 아우를 수는 없다는 한계가 있고,스텝 간의 이동
과첫 페이지로 돌아가기
와 같이 일관되지 않은 액션이 모두 setStep(number)으로 통일된다는 점에서 아직까지 직관성을 완벽히 보완했다고 하기 어렵다.따라서 더 고도화된 직관성을 위해 useReducer를 사용하였다.
useReducer를 사용하면 각 액션 단위로 로직을 지정할 수 있기 때문에
앞서 커버하지 못했던
스텝 간의 이동
도 함께 다룰 수 있으며,모든 액션을 setStep(number)로 통일하지 않고 더욱 구체적으로 구분할 수 있게 된다.
✔️ 결과 코드
🥺 소요 시간, 어려웠던 점
useState로 먼저 구현 -> useReducer 공부 -> 적용 방식 설계 후 구현
이 과정을 거치니까 useReducer를 왜 사용하는지, 어떻게 사용하면 될지 정확히 알고 최적화할 수 있던 것 같아서 유익하고 재밌었습니다🌈 구현 결과물
i.e.e.e.2023-11-10.i.i.11.11.11.mov
i.e.e.e.2023-11-10.i.i.11.12.12.mov
i.e.e.e.2023-11-10.i.i.11.12.41.mov