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주차 기본, 심화 과제 ] PICK YOUR BLACKUP #9

Merged
merged 43 commits into from
Jan 3, 2024
Merged

Conversation

lydiacho
Copy link
Contributor

@lydiacho lydiacho commented Nov 10, 2023

✨ 구현 기능 명세

🌱 기본 조건

  • 기본조건1

✅ 선택 과정은 총 3단계입니다. ( 3개 → 3개 → 2개)
✅ 아이템은 총 18개 이상입니다. (3 x 3 x 2 = 18)
위는 “최소”기준이며 그 이상의 개수는 가능합니다.

  • 기본조건2

✅ 전역상태관리 라이브러리, context 사용 금지 ❌
✅ Router 사용 금지 ❌


🧩 기본 과제

  1. 추천 종류 선택
    • 취향대로 추천 / 랜덤 추천 중 선택합니다.
    • 선택시 다음화면으로 넘어갑니다.

[취향대로 추천]

  1. 답변 선택

    • 호버시 스타일 변화가 있습니다.
    • 클릭시(선택시) 스타일 변화가 있습니다.
  2. 이전으로, 다음으로(결과보기) 버튼

    • 아무것도 선택되지 않았을 시 버튼을 비활성화 시킵니다.
      → 눌러도 아무 동작 X
      → 비활성화일 때 스타일을 다르게 처리합니다.
    • 이전으로 버튼을 누르면 이전 단계로 이동합니다.
    • 다음으로 / 결과보기 버튼을 누르면 다음 단계로 이동합니다.
    • 버튼 호버시 스타일 변화가 있습니다.
  3. 결과

    • 선택한 정보들에 맞는 결과를 보여줍니다.

[ 랜덤 추천 ]

  1. 숫자 카운트 다운
    • 3 → 2 → 1 숫자 카운트 다운 후 결과를 보여줍니다.
    • 추천 결과는 반드시 랜덤으로 지정합니다.

[ 공통 ]

  1. 결과 화면
    • 다시하기 버튼
      → 랜덤추천이면 랜덤 추천 start 화면으로, 취향대로 추천이면 취향대로 추천 start 화면으로 돌아갑니다.
      → 모든 선택 기록은 리셋됩니다.

🌠 심화 과제

  1. theme + Globalstyle 적용

    • 전역으로 스타일을 사용할 수 있도록 적용해보세요
  2. 애니메이션

    • 랜덤 추천 - 카운트다운에 효과를 넣어서 더 다채롭게 만들어주세요!
  3. 헤더

    • 처음으로 버튼
      → 추천 종류 선택 화면일시 해당 버튼이 보이지 않습니다.
      → 처음 추천 종류 선택 화면으로 돌아갑니다.
      → 모든 선택 기록은 리셋됩니다.

[ 취향대로 추천 ]

  1. 단계 노출

    • 3단계의 진행 단계를 보여줍니다.
  2. 이전으로 버튼

    • 이전으로 돌아가도 선택했던 항목이 선택되어 있습니다.
  • 6. useReducer , useMemo , useCallback 을 사용하여 로직 및 성능을 최적화합니다.

💎 PR Point

▶️ 컴포넌트 분리

컴포넌트를 다음과 같이 분리하였다.

  • App.jsx 형태

    ( 가독성을 위해 props 는 코드에서 삭제 )

    <Wrapper>
      <Header/>
      <ContentWrapper>
        {step === -1 ? (
          <Onboarding/>
        ) : step === 4 ? (
          <RandomMenu/>
        ) : step === choice.length ? (
          <ResultMenu/>
        ) : (
          <PickMenu/>
        )}
      </ContentWrapper>
    </Wrapper>
  • Header : 모든 flow와 단계에 걸쳐 동일하게 유지되는 페이지 최상단 헤더
    %E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202023-11-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011 26 57

  • Onboarding : 추천 종류 선택 단계 내부 UI
    %E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202023-11-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011 27 42

  • PickMenu : 취향대로 추천 flow 내부 UI
    %E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202023-11-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011 31 57

  • RandomMenu : 랜덤 추천 flow 내부 UI
    %E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202023-11-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011 32 30

  • ResultMenu : 결과 화면 UI
    %E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202023-11-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011 32 51

  • Layout 디렉토리

    • Title : 모든 flow, 단계에서 중복 사용되는 내부 헤더

      const Title = ({ children }) => {
        return (
          <Header>
            <h2>{children}</h2>
          </Header>
        );
      };
      %E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202023-11-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011 33 04
    • Button : 모든 단계에서 중복 사용되는 내부 버튼

      const Buttons = ({ children }) => {
        return <ButtonsWrapper>{children}</ButtonsWrapper>;
      };
      %E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202023-11-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011 33 50

▶️ 상태 관리

이번 과제는 하나의 페이지에 컴포넌트만 바꿔가며 렌더링함을 통해 여러 단계, 여러 flow를 구현해야 했다.
따라서 가장 중요시해야 하는 부분은 여러 컴포넌트를 오고갈 상태(state) 라고 생각했고,
과제의 모든 flow와 단계를 하나의 state로 관리했다.

// 선택 단계 state (여기!) 
const [step, setStep] = useState(-1);
// 각 step별 선택한 답변idx 배열 state
const [choice, setChoice] = useState(new Array(3));
// 추천 방식 state
const [howPick, setHowPick] = useState("");
  • -1 : 추천 방식 선택 화면
  • 0 : 취향대로 추천 1단계
  • 1 : 취향대로 추천 2단계
  • 2 : 취향대로 추천 3단계
  • 3 : 결과 화면
  • 4 : 랜덤 추천

이렇게 모든 단계와 flow를 하나의 state로 관리하니,
과제에서 요구했던

  • 취향대로 추천 단계 간 이동 시 데이터 유지
  • 취향대로 추천 시 단계 노출
  • 다시하기/처음하기 클릭 시 선택 기록 리셋
  • 디시하기/처음하기 클릭 시 최초 단계로 이동

위와 같은 조건을 매우 편리하게 충족시킬 수 있어서 좋았다.


그러나! 단계 뿐만 아니라 모든 flow와 결과 페이지 이동 등도 하나의 state로 관리하다 보니,
setState 사용의 목적이 코드에서 직관적으로 보이지 않는다는 한계점이 있었다.
이러한 한계점을 다음 내용인 useReducer로 최적화를 통해 개선할 수 있었다.


▶️ useReducer로 useState 사용 최적화해보기

🌱 관련커밋 : feat: setStep을 useReducer로 최적화하기

📚 참고문헌 : useReducer 공식문서

현재 step state에 부여되는 액션은 다음과 같다

  • 초기 : [step, setStep] = useState(-1)
  • Header.jsx : 처음으로 버튼 클릭 시 setStep(-1) → 최초 단계로 돌아가기
  • Onboarding.jsx : Start 버튼 클릭 시 howPick 값이 “취향대로 추천”이냐 “랜덤 추천”이냐에 따라
    • setStep(0) : 취향대로 추천 첫 화면으로 이동
    • setStep(4) : 랜덤 추천 화면으로 이동
  • PickMenu.jsx : 이전으로 버튼 클릭 시 setStep(step-1), 다음으로 버튼 클릭 시 setStep(step+1)
  • RandomMenu.jsx: time이 0이 되어 끝날 경우, setStep(3)으로 Result 페이지로 이동
  • ResultMenu.jsx: RETRY 버튼 클릭 시 setStep(-1) → 방식 선택한 첫 화면으로 돌아가기

이렇게 다양한 목적으로 사용됨! 정리하면 Action은

  • GO_BACK : 최초 페이지로 돌아가기 setStep(-1)
  • START_PICK : 취향대로 추천 첫 화면으로 이동 setStep(0)
  • START_RANDOM : 랜덤 추천 첫 화면으로 이동 setStep(4)
  • PREV : 이전 스텝으로 setStep(step-1)
  • NEXT : 다음 스텝으로 setStep(step+1)
  • GO_RESULT : result 페이지로 이동 setStep(3)

이번 프로젝트는 single page인 만큼
컴포넌트 간의 이동 로직이 불필요하게 복잡해지지 않게 구현하고자 이렇게 state 하나로 모든 이동을 구현하였지만, 이러한 방법은 직관성에 다소 치명적이다.
action에서 볼 수 있듯 일관성 있는 step이 아님에도 불구하고 -1부터 4까지의 숫자로 모두 나타냈기 때문에, 각 숫자가 의미하는 컴포넌트를 코드를 보고 쉽게 파악하기는 어렵다.

따라서 이러한 경우, 직관성을 높이기 위해 각 index가 가리키는 바를 상수 값으로 지정해줄 수 있다.

const FIRST_PAGE = -1;
const FIRST_PICK = 0;
const FIRST_RANDOM = 4;
const RESULT_PAGE = 3;

그러나 이럴 경우, 이전 스텝으로 이동, 다음 스텝으로 이동하는 액션까지 아우를 수는 없다는 한계가 있고,
스텝 간의 이동첫 페이지로 돌아가기와 같이 일관되지 않은 액션이 모두 setStep(number)으로 통일된다는 점에서 아직까지 직관성을 완벽히 보완했다고 하기 어렵다.

따라서 더 고도화된 직관성을 위해 useReducer를 사용하였다.
useReducer를 사용하면 각 액션 단위로 로직을 지정할 수 있기 때문에
앞서 커버하지 못했던 스텝 간의 이동 도 함께 다룰 수 있으며,
모든 액션을 setStep(number)로 통일하지 않고 더욱 구체적으로 구분할 수 있게 된다.

✔️ 결과 코드

  // 선택 단계 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);

🥺 소요 시간, 어려웠던 점

  • 2주 내내 걸쳐서 하루 30분~ 1시간 이상씩 10일정도 한 것 같아요
  • 프로젝트를 구현하기 전에 상태 설계를 구체적으로 하고 시작하니 전반적으로 큰 챌린징 요소 없이 구현할 수 있었습니다!
  • 오히려 처음에 블랙업 쇼핑몰에서 아이템 고르고, 이미지 가져오는 과정이 가장 오래 걸린 것 같습니다 ㅋㅎㅋㅎ
  • useReducer를 처음 사용해봤는데 useState로 먼저 구현 -> useReducer 공부 -> 적용 방식 설계 후 구현 이 과정을 거치니까 useReducer를 왜 사용하는지, 어떻게 사용하면 될지 정확히 알고 최적화할 수 있던 것 같아서 유익하고 재밌었습니다

🌈 구현 결과물

▶️ 취향대로 추천 flow

i.e.e.e.2023-11-10.i.i.11.11.11.mov

▶️ 랜덤추천 flow

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

Copy link
Member

@aazkgh aazkgh left a 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
Copy link
Member

Choose a reason for hiding this comment

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

음 이건 그냥 물어보는 질문인데! props를 여러 개 넘겨주는 건 아무 상관이 없나요??
예를 들어, useState를 남발하는 건 성능 최적화에 좋지 않잖아요! props를 여러 개 넘기는 건 아무런 문제가 없는지 궁금합니당!!!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

props를 여러개 넘겨주는건 성능에 큰 상관은 없지만, 다만 보통의 경우 넘겨주는 props의 개수가 많아질 경우 그 props를 받아서 사용하는 컴포넌트 내부의 로직이 복잡할 때가 많다...는 점이 있는데요!
제가 최근에 컴포넌트 합성과 분리에 대해 공부하면서 내린 결론은, 이 부분은 개발자의 성향에 따라 아래의 두가지로 나뉘어 선택할 수 있을 것 같아요!

  1. 한번에 props가 많이 전달되지 않도록 컴포넌트를 잘게 쪼개서 설계한다.
  2. 컴포넌트를 과도하게 잘게 쪼개기 보다 최소한의 컴포넌트를 생성하고 달라지는 데이터는 props들을 넘겨주어 처리한다

1번의 경우
하나의 컴포넌트 내부 로직이 복잡하지 않지만 컴포넌트가 과도하게 많이 생성된다.
2번의 경우
최소한의 컴포넌트로 최대의 재사용성을 뽑을 수 있지만 props를 활용하는 내부 로직이 복잡해지는 경우가 많다.


export default Header;

const HeaderWrapper = styled.header`
Copy link
Member

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.

네! 지난기수에서도 스타일드 컴포넌트를 별도의 파일로 관리하는 경우는 한번도..본적 없는것 같아요!
스타일드 컴포넌트는 말그대로 해당 파일, 해당 컴포넌트의 스타일링을 위해 생성해주는 친구이기 때문에 그 특정 컴포넌트의 UI 렌더링을 담당하는 파일 내에 함께 위치해주는 것이 적합하기 떄문에 대부분 이런 형태를 취하는 것 같습니다.
스타일드 컴포넌트를 별도의 파일로 관리하게 되면 오히려 불필요한 import문이 많아지는거라고 생각해요!!

import reset from "styled-reset";

export const GlobalStyle = createGlobalStyle`
${reset}
Copy link
Member

Choose a reason for hiding this comment

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

요것두 담에 한 번 써보려구용 ㅎㅅㅎ

Copy link

@unanchoi unanchoi left a comment

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 전달과 state 생성을 이렇게 깔꼼하게 처리할 수 있는 거였다니 .. 역시나 많이 배워갑니다아 !
수고했어요옹 !! 💗

Comment on lines +1 to +8
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";

Choose a reason for hiding this comment

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

외부 라이브러리랑 내가 작성한 모듈을 분리해서 import해주는게 좋을 것 같아요 ! (는 나도 늘 생각만 하고 못함)

Comment on lines +88 to +104
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};
`;

Choose a reason for hiding this comment

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

매번 승희 코드 리뷰할 때마다 CSS 순서가 지켜져 있는게 너무 깔끔하고 보기 좋아서 나도 이번 과제에 적용해보려 했는데 ! 얼레벌레 적용한 감이 있어서 (어떤 곳은 해두고 어떤 곳은 안 해두고 ㅋㅋ ㅜ) 아쉬웠는데 ..
승희 코드 보면서 다시한 번 습관을 들여야겠다 생각하고 갑니당

Comment on lines +16 to +30
& > 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;
}

Choose a reason for hiding this comment

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

헉 난 button wrapper 따로, button 따로 스타일링 지정 했는데 생각해보니 이렇게 wrapper에서 한 번에 버튼까지 스타일을 먹여줄 수 있겠구나 ? 메모메모

Comment on lines +6 to +12
const ResultMenu = ({ choice, setChoice, dispatch }) => {
const RESULT = DATA.find(
(el) =>
el.category === choice[0] &&
el.season === choice[1] &&
el.color === choice[2]
);

Choose a reason for hiding this comment

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

깔꼼해라

Comment on lines +16 to +34
// 선택 단계 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);

Choose a reason for hiding this comment

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

승희 PR 읽고 제 의견을 살짝 첨부해봅니당 !

useStateuseReducer 모두 상태값을 업데이트할 수 있는 Hook이여서 어느 경우에 선택하여 사용해야 하는 것인지 저도 고민을 많이했었는데 .. 저는 string, num과 같이 원시값을 초기값으로 가지고 로직에 따라 업데이트하는 경우에는 useState를 사용하고, 배열이나 객체와 같이 여러개의 필드들이 연관되어 업데이트 되어야하는 참조값을 초기값으로 가지는 경우나 여러개의 prop들이 연동되어 작동하는 경우에는 useReducer를 사용하는 것이 좋다고 생각합니다 !_!

승희가 PR에 남겨준 것 처럼 각 숫자가 의미하는 컴포넌트를 코드를 보고 쉽게 파악하기는 어렵다.는 점을 보완하기 위해서 useReducer로 리팩토링했다는 점을 PR을 읽을 때는 와닿지 않았는데, 코드리뷰를 하면서 RANDOM을 선택할 경우 step 이 4로 지정되는 부분을 보면서 승희가 무엇을 보완하고 싶었는지랑 useReducer을 왜 쓰게 되었는지 더 잘 이해할 수 있었고 고민과 공부의 흔적을 많이 볼 수 있었던 것 같아요!

저는 개인적으로 ! 승희가 구현한 로직에 따르면 기존에 구현했던 것 처럼 useState를 써서 구현하는 것도 괜찮을 것 같아요 ! 사실 step 하나만을 두고(useState 하나만으로 !) 전체 로직 flow를 처리할 수 있다는게 저는 오히려 더 간결하고 깔끔하게 로직을 구현한 것 같아서 감탄했답니다.

잘 정리된 아티클 하나 남기고 갑니다 ! 흐흐
useState VS useReducer

Choose a reason for hiding this comment

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

진짜 유용한 정보...감사합니다💛

Copy link

@rachel5640 rachel5640 left a comment

Choose a reason for hiding this comment

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

항상 정말 간결한 코드와 파일 구조, css 작성순서까지 하나하나 배울 점들이 정말 많은 코드를 보는 기분입니다..🥹 특히나 이번주는 프로젝트 초기에 객체 구조를 설계하는 방식이 이후 코드 작성을 훨씬 간편하게 만들어줄 수 있구나를 많이 배울 수 있었던 주였던 것 같아요.. 언니와 함께하는 잔디여서 너무 좋아요🍀...

Comment on lines +16 to +29
& > 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;

Choose a reason for hiding this comment

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

이렇게 만들면 따로 Wrapper, Button 컴포넌트를 따로 만들지 않고도 내부에 있는 요소에 스타일을 적용할 수 있군요🥹
이번 과제하면서 컴포넌트 임포트가 이렇게 길어져도 될까 고민을 정말 많이 했는데, 이런방식으로 파일의 갯수를 줄이는 방식도 있구나 배워갑니다!

Comment on lines +8 to +11
const fonts = {
kor: "font-family: 'Do Hyeon', sans-serif;",
eng: "font-family: 'Bebas Neue', sans-serif;",
};

Choose a reason for hiding this comment

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

리액트로 섞어짜기하는 사람은 언니밖에 없을꺼야...디자이너 아니냐구요...

Comment on lines +19 to +29
{CHOICE[step].map((el, idx) => (
<PickChoice
key={el}
onClick={() => {
const newChoice = [...choice];
newChoice[step] = idx;
setChoice(newChoice);
}}
$clicked={choice[step] === idx}>
{el}
</PickChoice>

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를 복사해서 사용하는데 이전에 선택했던 값을 얕게 복사해서 배열에 추가한 걸로 이해하면 될까용??

Copy link
Contributor Author

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 형태 : ['딸기', '사과']  -> 목적 달성! 

Comment on lines +3 to +5
const Buttons = ({ children }) => {
return <ButtonsWrapper>{children}</ButtonsWrapper>;
};

Choose a reason for hiding this comment

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

이렇게 만들면 시작하기와 이전으로 다음으로 wrapper를 별도를 분리해서 만들지 않아도 되군요!
승희언니 파일 구조를 보면서 어떻게 저렇게 컴포넌트 갯수를 적게 유지하면서 코드를 짰는지 궁금했는지 이런방식으로 줄일수도 있구나를 코드리뷰하면서 진짜 많이 깨닫게 되네요...🥹

Comment on lines +6 to +12
const ResultMenu = ({ choice, setChoice, dispatch }) => {
const RESULT = DATA.find(
(el) =>
el.category === choice[0] &&
el.season === choice[1] &&
el.color === choice[2]
);

Choose a reason for hiding this comment

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

진짜 깔끔한 코드 진짜로...대박.......천잰가.................
처음 객체 구조를 보면서 신기하다고 생각했는데 마지막 도출 과정에서 이렇게 사용하기 위해서
category, season, color에 값을 부여했군요... 너무 깔끔하네요..

Comment on lines +23 to +27
<OnboardingStartBtn
onClick={() => {
howPick === "취향대로 추천"
? dispatch({ type: "START_PICK" })
: dispatch({ type: "START_RANDOM" });

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을 지정하는 방식으로도 사용해서
이전으로 다음으로 버튼에도 적용할 수 있다는 점이 진짜 인상적이네용

Comment on lines +16 to +34
// 선택 단계 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);

Choose a reason for hiding this comment

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

진짜 유용한 정보...감사합니다💛

@lydiacho lydiacho merged commit 16bf60b into main Jan 3, 2024
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.

None yet

5 participants