Skip to content

Conversation

@dev-dino22
Copy link

@dev-dino22 dev-dino22 commented Apr 17, 2025

🎯 미션 소개

이번 미션을 통해 다음과 같은 학습 경험들을 쌓는 것을 목표로 합니다.

  • 재사용 가능한 Input Component를 개발한다.
  • Storybook을 사용하여 컴포넌트의 다양한 상태를 시각적으로 테스트한다.
  • 카드 정보를 효과적으로 렌더링 하기 위한 상태 관리를 경험한다.

안녕하세요, 동키콩! 카멜입니다🐪 좋은 한 주 보내고 계신가요?😃 1단계 기능 구현이 완료되어 미션 제출합니다!
controlled / uncontrolled 관점에서, 상태관리 관점에서, 훅의 사용에 대해서 나름 고민을 하면서 작성하였으나, 아직 리액트 적응단계라 감이 잘 안잡히는 것 같습니다🥲

🕵️ 셀프 리뷰(Self-Review)

제출 전 체크 리스트

  • 기능 요구 사항을 모두 구현했고, 정상적으로 동작하는지 확인했나요?
  • 기본적인 프로그래밍 요구 사항(코드 컨벤션, 에러 핸들링 등)을 준수했나요?
  • 배포한 데모 페이지에 정상적으로 접근할 수 있나요?

설계 설명

react-payments

기능 목록

사용자 입력

  • 사용자는 카드 번호를 입력할 수 있다.
    • 16자리의 입력 번호를 실시간 감지하여 알맞는 브랜드 로고를 UI에 표시한다. (Visa: 4로 시작 / MasterCard: 51~55로 시작 / 그외 로고 렌더링 x )
    • 예외: 숫자만 가능 / 16자리 / 한칸에 네 자리 -> 피드백: 빨간 border & 포커스 & 밑에 피드백 문구 출력
  • 사용자는 카드 유효기간을 입력할 수 있다.
    • 예외: 숫자만 가능 / 2자리 / 월은 112 까지 연도 2599까지 / 오늘 날짜를 기준으로 유효한 기간인지 검증 -> 피드백: 빨간 border & 포커스 & 밑에 피드백 문구 출력
  • 사용자는 CVC 번호를 입력할 수 있다.
    • 예외: 숫자만 가능 / 3자리 -> 피드백: 빨간 border & 포커스 & 밑에 피드백 문구 출력

UI 업데이트

  • 사용자 입력에 따라 동시에 프리뷰를 업데이트한다.
    • 입력할 때마다 한자리 단위로 실시간 업데이트
    • 카드번호에 유효한 로고 실시간 업데이트
    • 카드번호 뒷 8자리는 마스킹 처리

설계 구조

디렉터리 구조

src/
├── components/
 ├── common/
  └── inputForm/
  ├── InputForm.tsx
  └── input/
  └── Input.tsx

 ├── paymentInputPage/
  ├── cardInputForm/
   └── cardInput/
   ├── CardCVCInput.tsx
   ├── CardExperienceInput.tsx
   └── CardNumberInput.tsx
  └── cardPreview/
  └── CardPreview.tsx

컴포넌트 분리

상태 관리

paymentsInputPage는 cardInputForm의 상태를 cardPreview에 반영

  • cardInputForm 은 paymentsInputPage의 setState 콜백을 받아 자식들(card~input 컴포넌트들) 상태를 변경

  • paymentsInputPage는 state를 cardPreview에 전달

    • cardPreview는 state에 따라 실시간 변경

컨벤션

  • 폴더명은 카멜케이스로, 컴포넌트는 파스칼 케이스로 작성

CSS 작성 방식

  • Module CSS

리뷰 요청 & 논의하고 싶은 내용

1) 이번 단계에서 가장 많이 고민했던 문제와 해결 과정에서 배운 점

이번 미션에서 우선 고민이 되었던 부분은 스타일링 방식 선택이었습니다. styled-components는 컴포넌트 기반 개발 철학과 잘 맞고, 동적 스타일링에 강점을 갖고 있지만, 저는 기본적으로 CSS에 익숙하고, 스타일만을 위한 별도의 컴포넌트로 감싸야 한다는 점이 다소 번거롭게 느껴졌습니다. 특히 단순한 시각 스타일만 필요한 경우에는 오히려 가독성과 유지보수성을 해치는 구조라고 판단해 이번 미션에서는 module.css를 선택했습니다. 한 컴포넌트에서만 사용하는 css는 module css 로 관리하고, reset.css, color.css 등은 전역적으로 관리하였습니다.
이 방식이 코드와 스타일을 분리해 관리할 수 있고, 간단한 상태 기반 스타일링에도 충분하다고 느꼈습니다.

또한 코딩에 들어가기 전, 페어와 충분히 설계에 대해 논의하며 전체 흐름, 상태 관리 구조, 컴포넌트 분리 기준 등을 먼저 정리했습니다. 디렉터리 구조는 실제 DOM 트리 구조를 기준으로 나누어, 구조적으로 명확하게 파악할 수 있고 유지보수 시에도 직관적으로 접근할 수 있도록 했습니다. 그 과정에서 공통적으로 쓰이는 InputForm, Input 컴포넌트들을 common 디렉토리에 분리하게 되었습니다.

또한 처음엔 InputForm 이 Input 컴포넌트를 임포트하고 의존도를 갖는 식으로 작성하였지만, 확장성을 위해 children 으로 받을 수 있도록 열어두었습니다.

2) 이번 리뷰를 통해 논의하고 싶은 부분

CardCVCInput.tsx, CardExperienceInput.tsx, CardNumberInput.tsx 구조가 너무 유사한데, 이들을 하나의 컴포넌트로 추상화하는 것이 좋을지 고민하고 있습니다.
기본적으로 폼 구조도 유사하고 검증 방식도 비슷해보이지만, 너무 성급하게 추상화할 경우 오히려 가독성과 유지보수성을 해치거나 개발자 경험이 나빠질 수도 있겠다는 우려가 있습니다. 이 부분은 어떻게 생각하시나요?

저는 cardPreviewpaymentInputPage라는 전체 레이아웃 안에서 cardInputForm에 의존적인 역할을 수행하는 결합된 컴포넌트라고 판단하여 별도 컴포넌트로 분리하되, 같은 레벨 구조 하에 구성했습니다. 여러 곳에서 재사용될 여지가 적다고 생각해서 상태 관리를 paymentInputPage에서 통합한 구조인데요, 혹시 cardPreview를 별도로 더 분리하거나 상태 관리 책임을 다르게 두는 방식에 대해 어떤 의견을 가지고 계신가요?
이 두 부분은 팀 상황이나 설계 철학에 따라 방향이 다를 수 있다고 생각해서, 리뷰어님의 시각이 궁금합니다.

Storybook 스토리 구성에 대해서도 의견을 여쭙고 싶습니다.

훅을 사용하는 컴포넌트의 경우에는 render를 활용하여 내부 상태 변화가 보이도록 구성했고, 단순 UI 컴포넌트의 경우에는 args 기반으로 사용자에게 컨트롤이 가능한 스토리를 제공하도록 했습니다.
또한 에러 상태, 유효한 입력 상태 등 다양한 UI 시나리오가 보여질 수 있도록 Storybook에서 상태를 분리해 테스트했습니다.

이러한 방식이 스토리북 작성의 관점에서 적절했는지,
그리고 실제로 다양한 UI 상태가 명확하게 테스트된 것처럼 보이는지에 대해서도 리뷰어님의 피드백을 듣고 싶습니다.


✅ 리뷰어 체크 포인트

1. 컴포넌트 설계

  • 재사용성확장성을 고려해 Input 컴포넌트를 잘 설계했는가?
  • 컴포넌트를 분리할 기준(역할, 책임, 관심사 등)을 고민한 흔적이 보이는가?
  • UI 단위가 지나치게 작거나 크지 않고 적절한 수준으로 구성되어 있는가?

2. 상태 관리

  • 카드 정보 렌더링에 필요한 상태를 React의 상태로 적절하게 관리하고 있는가?

3. Storybook 활용

  • 컴포넌트의 다양한 상태(정상 입력, 오류, 빈값 등)를 확인할 수 있는 스토리가 작성되어 있는가?

4. UI/UX

  • 유효성 검증, 에러 메시지 등에서 사용자 경험을 고려한 처리가 이루어졌는가?

JeLee-river and others added 30 commits April 15, 2025 14:46
@dev-dino22 dev-dino22 changed the title Step1 [1단계 - 페이먼츠] 카멜(진나영) 미션 제출합니다. Apr 17, 2025
Copy link

@JUDONGHYEOK JUDONGHYEOK left a comment

Choose a reason for hiding this comment

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

안녕하세요! 카멜 이번 페이먼츠 미션의 리뷰를 맡게된 동키콩입니다! 카멜의 레벨2 첫 미션의 리뷰어를 맡게되어서 영광이네요! 레벨2의 첫 미션이니만큼 리액트의 기본동작들에 대해서 같이 잘 파헤쳐나가봅시다!

우선 리액트의 적응단계임에도 카멜의 생각대로 컴포넌트를 잘 분리해주신 것 같다고 생각이 들었어요! 저는 개인적으로 컴포넌트의 분리에 있어서 중요하다고 생각하는 부분이 역할의 분리인데 카멜의 컴포넌트는 역할에 따라 잘 분리되어 있다고 생각을 했습니다. 다만 도메인 로직들이 강하게 결합되어 있다보니 컴포넌트 내부로직을 파악하기 어렵고 무거워진다는 느낌이 들었어요. 이부분들은 같이 리뷰를 통해 의견을 주고받으면서 해결해보면 좋을 것 같아요!!

이번 리뷰를 통해 논의하고 싶은 부분

CardCVCInput.tsx, CardExperienceInput.tsx, CardNumberInput.tsx 구조가 너무 유사한데, 이들을 하나의 컴포넌트로 추상화하는 것이 좋을지 고민하고 있습니다.
기본적으로 폼 구조도 유사하고 검증 방식도 비슷해보이지만, 너무 성급하게 추상화할 경우 오히려 가독성과 유지보수성을 해치거나 개발자 경험이 나빠질 수도 있겠다는 우려가 있습니다. 이 부분은 어떻게 생각하시나요?

말씀해주신대로 성급한 추상화는 오히려 가독성이나 유지보수성 측면에서 안좋은 경험을 준다고 생각합니다. UI의 측면에서 바라본다면 적절하게 분리되어 있는 것 같다고 생각해요! 조금 더 가독성있게 구성할 수 있는 부분에 대해서는 코멘트로 남겨두었고 도메인 로직이 길어질 경우에 대해서는 추가적으로 고민해보면 좋을 것 같아요!

저는 cardPreview는 paymentInputPage라는 전체 레이아웃 안에서 cardInputForm에 의존적인 역할을 수행하는 결합된 컴포넌트라고 판단하여 별도 컴포넌트로 분리하되, 같은 레벨 구조 하에 구성했습니다. 여러 곳에서 재사용될 여지가 적다고 생각해서 상태 관리를 paymentInputPage에서 통합한 구조인데요, 혹시 cardPreview를 별도로 더 분리하거나 상태 관리 책임을 다르게 두는 방식에 대해 어떤 의견을 가지고 계신가요?
이 두 부분은 팀 상황이나 설계 철학에 따라 방향이 다를 수 있다고 생각해서, 리뷰어님의 시각이 궁금합니다

이건 어떤 질문인지 정확히 이해를 못해서 혹시 예시를 들어서 설명해주실 수 있으실까요??

Storybook 스토리 구성에 대해서도 의견을 여쭙고 싶습니다.
훅을 사용하는 컴포넌트의 경우에는 render를 활용하여 내부 상태 변화가 보이도록 구성했고, 단순 UI 컴포넌트의 경우에는 args 기반으로 사용자에게 컨트롤이 가능한 스토리를 제공하도록 했습니다.
또한 에러 상태, 유효한 입력 상태 등 다양한 UI 시나리오가 보여질 수 있도록 Storybook에서 상태를 분리해 테스트했습니다.
이러한 방식이 스토리북 작성의 관점에서 적절했는지,
그리고 실제로 다양한 UI 상태가 명확하게 테스트된 것처럼 보이는지에 대해서도 리뷰어님의 피드백을 듣고 싶습니다.

이 질문에 답변하기 전에 먼저 카멜에게 storybook을 통해 어떤 것을 테스트하고 싶었는지를 묻고 싶어요. storybook은 하나의 도구일 뿐이고 카멜이 어떤 것을 테스트하고 싶은 것에 따라 다르게 사용될 수 있다고 생각하기 때문에 카멜이 어떤 의도로 스토리파일들을 구성했는지 여쭤보고 싶어요!

리액트의 첫 미션임에도 기능상의 어색함이 적게 잘 구현해주신 것 같아요! 개인적으로 신선한 느낌의 코드들이 많아서 질문위주의 리뷰를 남겨두었던 것 같습니다. 같이 리뷰를 오고가면서 많은 의견들을 나눠보면 좋을 것 같아요! 코멘트 확인해주시고 다시 요청 부탁드리겠습니다!!🙇🏻‍♂️

type='tel'
name='cardExpirationDate'
placeholder={index === 0 ? 'MM' : 'YY'}
onChange={(e, setIsValid) => onChangeHandler(e, setIsValid, index)}

Choose a reason for hiding this comment

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

setIsValid를 onChangeHandler의 인자로 구성하는 것이 독특하다고 느꼈는데요. 혹시 이렇게 구성해주신 의도를 알 수 있을까요?

Copy link
Author

@dev-dino22 dev-dino22 Apr 21, 2025

Choose a reason for hiding this comment

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

음... 이 부분에 대해서도 작성 당시 페어와 고민을 많이 하였던 부분인데,
검증 로직은 외부에 위임하면서, 시각 피드백은 컴포넌트 내부에 남기고 싶어서 이렇게 결론이 났던 것 같습니다.

현재 구조에서는 모든 Input이 change 이벤트마다 유효성을 검사하고 피드백을 반영하므로 해당 로직을 Input 컴포넌트 내부에 두고 싶었는데,

지금 또 생각해보면 입력값 유효성 상태가 컴포넌트 내부에 국한되다 보니, 전체 폼의 유효성 판단이 어려워진다거나, 외부에서 유효성 피드백을 일괄 제어하기 어렵다는 점도 있는 것 같습니다.

이 부분은 미션 진행하면서 차근차근 구조적으로 다시 개선해보겠습니다!🥹

Choose a reason for hiding this comment

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

우선 Input컴포넌트가 웹표준의 인터페이스와 달라져 사용하는 쪽에서 사고의 과정이 하나 더 필요한 것도 허들이 될 수 있을 것 같구요. prop으로 에러상태를 전달해주는 것만으로도 충분할 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

확실히 prop으로 에러 상태를 전달받고 그에 따라 스타일이 바뀌는 정도만 Input에서 처리하는 게 좋은 것 같아요! 반영해보겠습니다!👍

Copy link
Author

Choose a reason for hiding this comment

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

반영하였습니다!
337f2d1

Choose a reason for hiding this comment

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

너무 좋습니다! 이전보다 사용처에서의 활용도 깔끔해진 것 같아요!

Choose a reason for hiding this comment

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

앗 그런데 story파일에서는 아직 인터페이스 변경이 반영되지 않아서 타입에러가 나고 있네요 확인부탁드립니다!

Copy link
Author

@dev-dino22 dev-dino22 Apr 23, 2025

Choose a reason for hiding this comment

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

헉!!!! 스토리파일...!!!!!!! 생각도 못했어요... 리뷰요청 전에 꼼꼼히 생각하고 봤어야했는데 죄송해요😔 발견해주셔서 감사합니다🥹 확인하고 바로 수정하겠습니다!

Copy link
Author

@dev-dino22 dev-dino22 left a comment

Choose a reason for hiding this comment

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

저는 cardPreview는 paymentInputPage라는 전체 레이아웃 안에서 cardInputForm에 의존적인 역할을 수행하는 결합된 컴포넌트라고 판단하여 별도 컴포넌트로 분리하되, 같은 레벨 구조 하에 구성했습니다. 여러 곳에서 재사용될 여지가 적다고 생각해서 상태 관리를 paymentInputPage에서 통합한 구조인데요, 혹시 cardPreview를 별도로 더 분리하거나 상태 관리 책임을 다르게 두는 방식에 대해 어떤 의견을 가지고 계신가요?
이 두 부분은 팀 상황이나 설계 철학에 따라 방향이 다를 수 있다고 생각해서, 리뷰어님의 시각이 궁금합니다

이건 어떤 질문인지 정확히 이해를 못해서 혹시 예시를 들어서 설명해주실 수 있으실까요??

앗 상태관리에 대한 질문이었습니다!
제가 생각한 cardPreview의 역할은 단순 카드모양 UI 렌더링이 아닌
"새로 들어오는 상태에 따라 실시간 렌더링 업데이트로 완성되는 카드UI"
였습니다.

단순 카드모양 UI였다면 구조가 다른 페이지에서도 재사용될 여지가 있음을 염두에 두고 확장성을 고려해 설계하였겠지만, input에 따른 실시간 렌더링이 핵심 역할이라면 구조가 다른 페이지에서 사용될 여지까지 고려해 확장성을 넓히는 것보다 카드정보인풋 컴포넌트에 의존성을 갖게되더라도 현재의 구조로 작성하는 게 낫겠다고 판단했습니다.

그래서 현재는 paymentInputPage라는 컴포넌트가 카드 UI와 인풋 UI를 결합시키고 있고, 관련 상태도 paymentInputPage에서 정의하면서 인풋에서 상태를 끌어올려 카드 UI에 넣어주는 구조입니다.

이러한 판단에 대해서 동키콩의 의견을 들어보고싶었어요!😊

예를 들어 전역 상태로 관리하는 방법이나,
현재 분산되어있는 두 상태가 결국 카드정보라는 하나의 공통점을 가지므로 하나의 커스텀 훅을 만들어 관리하고 유효성 검사도 이 커스텀 훅에서 useEffect로 처리시킨다거나,
이렇게 한다면 cardPreview가 단순 상태만 전달받지않고 setState 훅도 전달받을 수 있으니까요!

다시 읽어보니까 두서없이 질문을 드렸던 것 같네요🥹

@dev-dino22
Copy link
Author

dev-dino22 commented Apr 21, 2025

안녕하세요, 동키콩! 지난 리뷰에서 주신 피드백들을 바탕으로 다음과 같은 사항들을 수정 및 반영했습니다.

  • 불필요한 Fragment 제거: CardCVCInput.tsxCardNumberInput.tsx에서 불필요한 <> Fragment를 제거했습니다.
  • CardCVCInput.tsx return 문 제거: 불필요한 return 문을 삭제했습니다.
  • 유효성 검사 시 isValid 상태 업데이트: 카드 번호 입력 시 유효한 값으로 변경될 때 isValid 상태를 true로 업데이트하는 로직을 추가했습니다.
  • CardExpirationDateInput.tsx Import 경로 수정: 지적해주신 Import 경로 오류를 수정하였습니다.
  • CardExpirationDateInput.tsx onChangeHandler 함수 분리: 함수 내 반복되는 로직 및 상수를 분리하여 가독성을 높였습니다.
  • CardExpirationDateInput.tsx inputs Map 함수 개선: index 대신 배열을 사용하여 map 함수를 구현하여 더욱 선언적인
  • **Input 컴포넌트에서 불필요한 forwardRef제거 및 props 전개 방식으로 가독성 개선

코드로 변경했습니다.

아직 고민 중인 부분은
useState훅을 사용하기엔 상호적이고 직접적인 상태 변경 및 관리가 필요하지 않을 때 useMemo() 훅 사용에 대하여

Input 컴포넌트의 onChangeHandler와 isValid 에 대하여

입니다...! 미션 진행하면서 동키콩과 의견을 나누고 생각을 발전시켜나가보고 싶어요!
동키콩이 꼼꼼하게 리뷰해주신 덕분에 개선할 부분을 명확히 파악하고 수정할 수 있었습니다! 감사합니다 ㅎㅎ

Copy link

@JUDONGHYEOK JUDONGHYEOK left a comment

Choose a reason for hiding this comment

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

안녕하세요 카멜!! 아직 import문이 정리가 되지 않은 것 같아요! 그리고 import되지 않은 상수를 사용하고 있다보니 버그도 있는 것 같습니다! 확인해주시고 다시 요청 주시면 감사하겠습니다!

type='tel'
name='cardExpirationDate'
placeholder={index === 0 ? 'MM' : 'YY'}
onChange={(e, setIsValid) => onChangeHandler(e, setIsValid, index)}

Choose a reason for hiding this comment

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

우선 Input컴포넌트가 웹표준의 인터페이스와 달라져 사용하는 쪽에서 사고의 과정이 하나 더 필요한 것도 허들이 될 수 있을 것 같구요. prop으로 에러상태를 전달해주는 것만으로도 충분할 것 같아요!

Comment on lines 21 to 22
setFeedbackMessage('숫자만 입력 가능합니다.');
setIsValid(false);

Choose a reason for hiding this comment

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

단순 feedBackMessage의 유무로도 판단할 수 있을 것 같은데 valid상태를 별도로 input에서 관리하다보니 함수들도 복잡해지는 것 같습니다!

Copy link
Author

Choose a reason for hiding this comment

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

원래는 is- prefix로 유효한지에 대한 상태를 명시적으로 관리하고싶어서 따로 분리하였는데
불필요한 상태를 만들어서 함수가 복잡하고 관리포인트가 늘어난다는 점에서 개선할 필요를 느끼게 되었습니다!

또한 이전의 구조상 Input 이 유효한지에 대해 인풋컴포넌트에서 자체 상태를 갖고있었으나, 가독성이 떨어지고 흐름을 알기 어려우며 확장성이 떨어진다는 점에서 Input 컴포넌트는 isValid의 상태를 프롭으로 전달받고, 사용처에서 상태를 관리하는 것으로 리팩토링하였습니다.

그래서 Input과 Input 컴포넌트를 사용하는 Card-Input 컴포넌트들을 전체적으로 리팩토링하였고,
CardInput컴포넌트들은 상태도 하나의 상태를 객체 형태로 관리하게 되었습니다.

  const [expiration, setExpiration] = useState({
    month: "",
    year: "",
    feedbackMessage: {
      month: "",
      year: "",
    },
  });

이렇게 리팩토링을 진행하면서 isValid의 상태는 말씀해주신 것처럼 feedbackMessage가 빈문자열인지 아닌지를 기준으로 판단하는 것으로 바꾸었습니다!😊

337f2d1

Choose a reason for hiding this comment

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

오 좋습니다 비제어 컴포넌트에 대한 시도도 좋았어요! 추후에 validation시점에 따라서 충분히 잘 활용할 수 있을거라 생각합니다!

추가로 궁금한점은 input에서 expiration이나 cardNumber같은 상태를 추가로 관리해주시는 이유가 궁금해요! 제 생각에는 동일한 데이터를 두개의 source로 관리하다보니 더 관리가 까다로울 것 같아서요! 혹시 다른 이유가 있으실지 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

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

단순히 각 상태가 다른 컴포넌트에서 관리되고 넘겨주어야함을 강조하기 위해 따로 분리해서 관리했었습니다.
하지만 지금 생각해보니 cardPreview는 card~Input 컴포넌트들에 의존하고있으므로 paymentInputPage 가 상태관리를 하기로 결정한 맥락처럼 굳이 같이 있을 카드번호와 유효기간의 상태를 따로 관리할 필요는 없었던 것 같아요!

2단계 진행하면서 상태가 많이 추가될 것 같은데 고려하면서 리팩토링 해보겠습니다!👍👍

@dev-dino22
Copy link
Author

안녕하세요 동키콩!😊 카멜입니다!
피드백 주신 부분 반영하여 다시 리뷰요청 드립니다! 이번 피드백까지 받고나니 Input 컴포넌트와 Card-Input 컴포넌트들의 기존 피드백이 어떤 의미였는지 조금 더 명확히 와닿아서 controlled vs uncontrolled 선택에 대한 고민과 적합한 훅의 사용에 대해 고민해볼 수 있었던 것 같습니다!

그렇게 제가 이해한 방향대로 리팩토링을 진행해보았는데 말씀해주신 방향과 제가 이해한 방향이 맞는지 확인 부탁드립니당!🙇🏻‍♀️✨

추가로 궁금한 점이 두가지 있습니다!

1. 현재 상태관리

현재의 CardPreview와 Card-Input 컴포넌트들이 PaymentInputPage 컴포넌트 안에서 결합이 되어있는 현재의 상태관리(Card-Input 컴포넌트에서의 상태를 끌어올려 CardPreview에 넣어주는 방식)가 괜찮은 선택일지 동키콩의 의견도 궁금합니다!
#435 (comment)

2. 커밋을 나누는 기준

그리고 이건 코딩에 대한 궁금증은 아니지만, 커밋을 나누는 기준에 대한 고민이 있는데요!
337f2d1
이 커밋의 경우 refactor: Input 컴포넌트는 isValid 프롭을 전달받고 각 유효 상태를 사용처에서 제어하도록 변경 및 에러메세지 상수분리와 에러메세지 함수 작성 의 변경사항이 들어있고, 변경된 파일도 Input과 관련된 모든 파일들입니다.

그동안 저는 커밋의 단위는 프로그램이 깨지지 않는 단위여야한다고 들었던 것 같아서 해당 기준을 잡고 커밋을 해왔습니다.

하지만 Input같은 다른 컴포넌트들이 의존하고있는 컴포넌트를 수정하게되면 한 커밋에서 변경되는 파일이 너무 많아서 커밋의 가독성(?)이 떨어지는 것이 고민입니다... 그래서 동키콩의 커밋을 나누는 기준도 궁금합니다! 🥹

항상 꼼꼼하게 고민해볼 수 있는 좋은 피드백 감사드려요!ㅎㅎ

@JUDONGHYEOK
Copy link

앗 제가 생각해보니 이전 질문에 대한 답변을 안 드렸던 것 같아요!

앗 상태관리에 대한 질문이었습니다!
제가 생각한 cardPreview의 역할은 단순 카드모양 UI 렌더링이 아닌
"새로 들어오는 상태에 따라 실시간 렌더링 업데이트로 완성되는 카드UI"
였습니다.

단순 카드모양 UI였다면 구조가 다른 페이지에서도 재사용될 여지가 있음을 염두에 두고 확장성을 고려해 설계하였겠지만, input에 따른 실시간 렌더링이 핵심 역할이라면 구조가 다른 페이지에서 사용될 여지까지 고려해 확장성을 넓히는 것보다 카드정보인풋 컴포넌트에 의존성을 갖게되더라도 현재의 구조로 작성하는 게 낫겠다고 판단했습니다.

그래서 현재는 paymentInputPage라는 컴포넌트가 카드 UI와 인풋 UI를 결합시키고 있고, 관련 상태도 paymentInputPage에서 정의하면서 인풋에서 상태를 끌어올려 카드 UI에 넣어주는 구조입니다.

이러한 판단에 대해서 동키콩의 의견을 들어보고싶었어요!😊

예를 들어 전역 상태로 관리하는 방법이나,
현재 분산되어있는 두 상태가 결국 카드정보라는 하나의 공통점을 가지므로 하나의 커스텀 훅을 만들어 관리하고 유효성 검사도 이 커스텀 훅에서 useEffect로 처리시킨다거나,
이렇게 한다면 cardPreview가 단순 상태만 전달받지않고 setState 훅도 전달받을 수 있으니까요!

paymentInputPage에서 상태를 관리하는 현재 설계는 충분히 합리적이라고 생각합니다. cardPreview와 CardInputForm이 모두 카드 정보를 필요로 하니까, 이들을 결합하는 상위 컴포넌트에서 상태를 관리하는 게 깔끔하죠. 약간의 prop 드릴링이 있지만, 현재 복잡도가 높지 않으니 문제없다고 봅니다. 유사한 문제의 해결을 위해 React Hook Form이나 Formik 같은 라이브러리도 Provider를 통해 하위 컴포넌트에서 폼 상태를 쉽게 쓰도록 설계되었는데, 지금은 그 정도 복잡성이 필요 없어 보여요.

질문하신 커스텀 훅은 상태를 공통으로 관리해야 하다 보니 결국 prop으로 전달해야 하는 부분은 비슷할 거예요.
추가로 상태에 관해 궁금한점은 코멘트로 남겨두겠습니다!

@JUDONGHYEOK
Copy link

  1. 커밋을 나누는 기준
    그리고 이건 코딩에 대한 궁금증은 아니지만, 커밋을 나누는 기준에 대한 고민이 있는데요!
    337f2d1
    이 커밋의 경우 refactor: Input 컴포넌트는 isValid 프롭을 전달받고 각 유효 상태를 사용처에서 제어하도록 변경 및 에러메세지 상수분리와 에러메세지 함수 작성 의 변경사항이 들어있고, 변경된 파일도 Input과 관련된 모든 파일들입니다.

그동안 저는 커밋의 단위는 프로그램이 깨지지 않는 단위여야한다고 들었던 것 같아서 해당 기준을 잡고 커밋을 해왔습니다.

하지만 Input같은 다른 컴포넌트들이 의존하고있는 컴포넌트를 수정하게되면 한 커밋에서 변경되는 파일이 너무 많아서 커밋의 가독성(?)이 떨어지는 것이 고민입니다... 그래서 동키콩의 커밋을 나누는 기준도 궁금합니다! 🥹

항상 꼼꼼하게 고민해볼 수 있는 좋은 피드백 감사드려요!ㅎㅎ

커밋을 나누는 것은 항상 고민이 되는 포인트인 것 같아요. 커밋을 나누는 기준은 말씀하신대로 논리적인 단위로 구분이 되면서 프로그램이 정상작동하여야하고 적절한 크기여야합니다. 하지만 말씀해주신대로 input과 같은 컴포넌트의 인터페이스가 변경이 된다면 어쩔 수 없이 커밋이 커지기도 하는 것 같습니다. 작업단위가 너무 커져서 파악이 안된다 싶으면 옵셔널한 인터페이스를 구성해서 작업단위를 줄일 수도 있겠죠. 하지만 지금정도는 사실 괜찮다고 봅니다.

예시로 들어주신 커밋은 이와는 별개로 input의 인터페이스를 변경하고 리팩토링까지 같이 포함되어 있으니 조금 범위가 넓을 수도 있을 것 같아요. 크게 문제가 되는 상황은 아니라고 보지만, 커밋을 잘게 나누려는 의식 자체가 훌륭하다고 말씀드리고 싶습니다!

Copy link

@JUDONGHYEOK JUDONGHYEOK left a comment

Choose a reason for hiding this comment

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

안녕하세요 카멜!! step2를 시작해야할 시기인 것 같아 후다닥 달려왔습니다! 리뷰 반영해주신부분 잘 확인했습니다! 질문주신 부분들에 대해서는 별도의 코멘트로 작성했으니 확인부탁드려요!!

현재 story파일에서 input인터페이스의 변경이 반영되지 않은 상태인데요. 한번 확인 부탁드립니다!

기능상의 문제는 없고 코드도 이전보다 깔끔해진것 같습니다. 이제 step2를 시작해야할 것 같아 남은 코멘트들은 step2를 진행하시면서 확인하고 반영해주시면 좋을 것 같습니다!

1단계도 너무 수고많으셨어요! 카멜과의 티키타카를 통해 저도 성장한 느낌이 들어 너무 좋은 경험이였습니다! 2단계도 화이팅해보시죠!!!

@JUDONGHYEOK JUDONGHYEOK merged commit c4e7d81 into woowacourse:dev-dino22 Apr 23, 2025
dev-dino22 added a commit to dev-dino22/react-payments that referenced this pull request Apr 28, 2025
* feat: 페어 프로그래밍 초기 세팅

Co-authored-by: dev-dino22 <[email protected]>

* docs: 미션의 기능목록, 설계 구조 등 작성

Co-authored-by: dev-dino22 <[email protected]>

* style: 전역 스타일링 적용

Co-authored-by: dev-dino22 <[email protected]>

* feat: Input 컴포넌트 생성

Co-authored-by: dev-dino22 <[email protected]>

* feat: InputForm 컴포넌트 생성

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 번호 입력 컴포넌트 작성

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 유효기간 입력 컴포넌트 작성

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 CVC 입력 컴포넌트 작성

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 입력 form 컴포넌트 작성

Co-authored-by: dev-dino22 <[email protected]>

* refactor: InputForm이 Input을 children으로 받도록 리팩토링

Co-authored-by: dev-dino22 <[email protected]>

* style: 전역 변수로 관리하는 색상 추가

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 브랜드 로고 이미지 생성

Co-authored-by: dev-dino22 <[email protected]>

* style: 카드 입력 form 컴포넌트 스타일 적용

Co-authored-by: dev-dino22 <[email protected]>

* style: 기본 스타일을 초기화하는 파일 수정

Co-authored-by: dev-dino22 <[email protected]>

* style: 카드 입력 컴포넌트의 스타일 수정

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 정보를 입력하는 UI를 렌더링하는 함수 생성

Co-authored-by: dev-dino22 <[email protected]>

* style: 카드 정보를 입력하는 UI에 스타일 추가

Co-authored-by: dev-dino22 <[email protected]>

* style: 카드 입력 form의 스타일 조정

Co-authored-by: dev-dino22 <[email protected]>

* feat: 사용자가 입력한 카드 정보를 프리뷰에 렌더링하는 기능 구현

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 번호와 유효기간 정보를 상태관리 하도록 코드 추가

Co-authored-by: dev-dino22 <[email protected]>

* refactor: 사용자 카드 정보 등록 페이지가 렌더링되도록 app 수정

Co-authored-by: dev-dino22 <[email protected]>

* feat: 사용자 입력값의 예외 처리 로직 생성

Co-authored-by: dev-dino22 <[email protected]>

* feat: 사용자 입력값에 대한 피드백 메시지 렌더링 로직 추가

Co-authored-by: dev-dino22 <[email protected]>

* refactor: 유효기간 검증 로직 수정

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 유효기간 검증 로직 추가

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 CVC 검증 로직 추가

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 번호 검증 로직 추가

Co-authored-by: dev-dino22 <[email protected]>

* docs: 구현한 기능 목록 체크

* refactor: 상태 변경 함수의 타입 추가

* style: storybook에 css 적용

Co-authored-by: dev-dino22 <[email protected]>

* refactor: 불필요한 props 제거

Co-authored-by: dev-dino22 <[email protected]>

* test: storybook 비주얼 테스트 추가

Co-authored-by: dev-dino22 <[email protected]>

* fix: 웹 배포 환경에 따라 이미지 경로 수정

* refactor: 불필요한 fragment 제거

* refactor: 카드 정보 상수 분리

* refactor: checkBrand 조건문 startsWith()로 가독성 개선

* refactor: 다시 유효한 값 입력 시 오류 스타일 삭제

* refactor: 유효기간 month, year로 나누고 전용 validator 분리

* refactor: 전역 상태 대신 ref로 유효기간 검증 관리

* refactor: 카드 유효기간입력 컴포넌트의 inputs를 map 함수 렌더링으로 변경

* refactor: CardExpirationDateInput 의 onChangeHandler 함수/상수 분리 및 추상화

* refactor: 카드프리뷰의 let 을 사용한 함수 내 상태관리에서 useMemo()훅 활용

* refactor: Input 컴포넌트에서 이제 불필요한 forwardRef 제거 및 props 전개 방식으로 변경

* chore: Input컴포넌트 폴명 파스칼케이스 수정을 위한 중간임시 폴더명

* feat: input 폴더명 카멜케이스 수정

* refactor: validateCardInput.ts 의 숫자문자열 검사 메서드명 수정

* refactor: validateCardInput.ts의 반환값 에러결과불리언객체로 통일

* refactor: Input 컴포넌트는 isValid 프롭을 전달받고 각 유효 상태를 사용처에서 제어하도록 변경 및 에러메세지 상수분리와 에러메세지 함수 작성

* chore: 불필요한 import 문 제거

---------

Co-authored-by: Jeongeun Lee <[email protected]>
Co-authored-by: dev-dino22 <[email protected]>
JUDONGHYEOK pushed a commit that referenced this pull request May 7, 2025
* initial: 프로젝트 세팅

* feat: selectBox 컴포넌트 작성

* feat: 카드 입력 폼 브랜드 셀렉트 구현

* refactor: PaymentInputPage 컴포넌트의 각 상태를 하나의cardInfo 상태로 통합

* feat: 카드 브랜드 선택에 따라 카드 프리뷰의 색상 변경 기능 구현

* feat: 카드 번호 및 유효기간 입력 시 다음 필드로 포커스 이동 기능 추가

* feat: 카드 비밀번호 입력 컴포넌트 작성

* chore: PaymentInputPage 모듈 css 높이 설정 및 CardInfo 에 비밀번호 컴포넌트 상수 작성

* feat: 값이 유효하면 다음 입력 컴포넌트 렌더링 기능 구현

* refactor: react router 페이지기능 도입 전 디렉터리 구조 변경 - pages 폴더 분류

* refactor: pages 폴더명 케밥케이스로 변경 및 PAGE_URL 상수 작성

* feat: NotFoundPage 컴포넌트 작성 및 Route 연결

* feat: 페이먼츠 메인페이지 작성

* feat: 카드 등록 성공 페이지 작성

* refactor: 디렉터리 네이밍 페이지 폴더는 케밥케이스로 통일

* style: CSS 스타일 수정 및 컴포넌트 레이아웃 개선

* feat: 버튼 컴포넌트 추가 및 카드 입력 폼에서 사용, 성공 페이지에서 카드 정보 표시 수정

* refactor: setCardInfo 훅을 각 용도별 핸들러를 만들어 프롭 전달하는 구조로 개선

* refactor: 카드 입력 컴포넌트 리팩터링

- CardPasswordInput과 CardExpirationDateInput 컴포넌트를 제거
- CardNumberInput 컴포넌트를 개선된 검증 및 상태 관리 로직으로 새로 작성
- 관심사 분리를 위해 usePasswordInput, useCardNumberInput 훅을 추가
- 카드 브랜드 및 에러 메시지 상수를 정의
- 폼 검증 상태를 관리하는 useFormState 훅을 구현
- 카드 입력 검증을 위한 validator 함수를 수정

* refactor: 컴포넌트의 상태 관리 개선 및 타입 안전성 강화

* feat: 다음 인풋 활성화 시 자동 포커스 구현

* test: 카드 입력 및 성공 페이지, 카드 미리보기 및 입력 필드 컴포넌트 스토리북 작성 및 개선

* fix: gitpages 배포 위해 BrouserRouter에서 HashRouter로 변경

* fix: CVC 입력 및 유효기간 입력 조건 분기 수정 및 유효기간 연도 입력 시 달이 비어있으면 헤당 포커스 이동 기능 추가

* feat: SPA BrowserRouter 배포 설정

* refactor: 404.html 퍼블릭 폴더

* chore: 스토리북 css 임포트 경로 수정

* fix: storybook index.css 경로 수정 및 글로벌 css를 src 내부 글로벌 폴더로 이동

* test: storybook meta 설정 수정

* refactor: package.json 크로매틱 환경변수로 배포

* init project

* feat: init react payments project

* [1단계 - 페이먼츠] 카멜(진나영) 미션 제출합니다. (#435)

* feat: 페어 프로그래밍 초기 세팅

Co-authored-by: dev-dino22 <[email protected]>

* docs: 미션의 기능목록, 설계 구조 등 작성

Co-authored-by: dev-dino22 <[email protected]>

* style: 전역 스타일링 적용

Co-authored-by: dev-dino22 <[email protected]>

* feat: Input 컴포넌트 생성

Co-authored-by: dev-dino22 <[email protected]>

* feat: InputForm 컴포넌트 생성

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 번호 입력 컴포넌트 작성

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 유효기간 입력 컴포넌트 작성

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 CVC 입력 컴포넌트 작성

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 입력 form 컴포넌트 작성

Co-authored-by: dev-dino22 <[email protected]>

* refactor: InputForm이 Input을 children으로 받도록 리팩토링

Co-authored-by: dev-dino22 <[email protected]>

* style: 전역 변수로 관리하는 색상 추가

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 브랜드 로고 이미지 생성

Co-authored-by: dev-dino22 <[email protected]>

* style: 카드 입력 form 컴포넌트 스타일 적용

Co-authored-by: dev-dino22 <[email protected]>

* style: 기본 스타일을 초기화하는 파일 수정

Co-authored-by: dev-dino22 <[email protected]>

* style: 카드 입력 컴포넌트의 스타일 수정

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 정보를 입력하는 UI를 렌더링하는 함수 생성

Co-authored-by: dev-dino22 <[email protected]>

* style: 카드 정보를 입력하는 UI에 스타일 추가

Co-authored-by: dev-dino22 <[email protected]>

* style: 카드 입력 form의 스타일 조정

Co-authored-by: dev-dino22 <[email protected]>

* feat: 사용자가 입력한 카드 정보를 프리뷰에 렌더링하는 기능 구현

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 번호와 유효기간 정보를 상태관리 하도록 코드 추가

Co-authored-by: dev-dino22 <[email protected]>

* refactor: 사용자 카드 정보 등록 페이지가 렌더링되도록 app 수정

Co-authored-by: dev-dino22 <[email protected]>

* feat: 사용자 입력값의 예외 처리 로직 생성

Co-authored-by: dev-dino22 <[email protected]>

* feat: 사용자 입력값에 대한 피드백 메시지 렌더링 로직 추가

Co-authored-by: dev-dino22 <[email protected]>

* refactor: 유효기간 검증 로직 수정

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 유효기간 검증 로직 추가

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 CVC 검증 로직 추가

Co-authored-by: dev-dino22 <[email protected]>

* feat: 카드 번호 검증 로직 추가

Co-authored-by: dev-dino22 <[email protected]>

* docs: 구현한 기능 목록 체크

* refactor: 상태 변경 함수의 타입 추가

* style: storybook에 css 적용

Co-authored-by: dev-dino22 <[email protected]>

* refactor: 불필요한 props 제거

Co-authored-by: dev-dino22 <[email protected]>

* test: storybook 비주얼 테스트 추가

Co-authored-by: dev-dino22 <[email protected]>

* fix: 웹 배포 환경에 따라 이미지 경로 수정

* refactor: 불필요한 fragment 제거

* refactor: 카드 정보 상수 분리

* refactor: checkBrand 조건문 startsWith()로 가독성 개선

* refactor: 다시 유효한 값 입력 시 오류 스타일 삭제

* refactor: 유효기간 month, year로 나누고 전용 validator 분리

* refactor: 전역 상태 대신 ref로 유효기간 검증 관리

* refactor: 카드 유효기간입력 컴포넌트의 inputs를 map 함수 렌더링으로 변경

* refactor: CardExpirationDateInput 의 onChangeHandler 함수/상수 분리 및 추상화

* refactor: 카드프리뷰의 let 을 사용한 함수 내 상태관리에서 useMemo()훅 활용

* refactor: Input 컴포넌트에서 이제 불필요한 forwardRef 제거 및 props 전개 방식으로 변경

* chore: Input컴포넌트 폴명 파스칼케이스 수정을 위한 중간임시 폴더명

* feat: input 폴더명 카멜케이스 수정

* refactor: validateCardInput.ts 의 숫자문자열 검사 메서드명 수정

* refactor: validateCardInput.ts의 반환값 에러결과불리언객체로 통일

* refactor: Input 컴포넌트는 isValid 프롭을 전달받고 각 유효 상태를 사용처에서 제어하도록 변경 및 에러메세지 상수분리와 에러메세지 함수 작성

* chore: 불필요한 import 문 제거

---------

Co-authored-by: Jeongeun Lee <[email protected]>
Co-authored-by: dev-dino22 <[email protected]>

* refactor: react router 페이지기능 도입 전 디렉터리 구조 변경 - pages 폴더 분류

* feat: SPA BrowserRouter 배포 설정

* chore: 스토리북 css 임포트 경로 수정

* fix: rebase 충돌로 인해 포함된 불필요한 파일 제거

* chore: PAGE_URL 의 ADD_CARD.SUCCESS 오타 수정

* refactor: useFormState() 훅의 handle-* 추상화

* refactor: CardPreview의 style 삼항 연산자 가독성 개선

* refactor: AddCardSuccessPage의 이중 import문 수정

* refactor: isVisibleSubmitButton을 상태로 관리하지 않고 useMemo를 사용해 파생 상태로 관리하도록 개선

* refactor: PaymentInputPage.tsx의 handleInputChange 함수 추상화

* style: SelectBox 선택 시 글자와 ArrowSVG 컬러 검은색으로 변경

* refactor: useNumberInput의 onChangeHandler 내부 로직 함수 분리 시도

---------

Co-authored-by: devJang <[email protected]>
Co-authored-by: Jeongeun Lee <[email protected]>
Co-authored-by: dev-dino22 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants