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

[1단계 - 페이먼츠 미션] 지그(송지은) 미션 제출합니다. #17

Merged
merged 81 commits into from
May 2, 2021

Conversation

zigsong
Copy link

@zigsong zigsong commented Apr 29, 2021

안녕하세요, 리뷰어님! 조금 늦었지만 😅
페이먼츠 미션 1단계 제출합니다!

미션 figma가 375 X 700 px로 맞춰져 있어, 해당하는 뷰포트에 맞춰 제작했습니다.
화면 크기를 조정해서 봐주시면 더욱 편하게 보실 수 있을 것 같습니다!

💳 데모 페이지

💻 시연

payments

💾 컴포넌트 구조

CardRegister

CardCompletion

🤔 고민한 부분

  1. Router를 사용하지 않고 '카드 등록 페이지'와 '카드 등록 완료 페이지', 그리고 2단계에서 구현할 '카드 목록 페이지'를 구현하기 위해 현재 App.js에서 PAGES라는 상수를 선언해 놓고 currentPage라는 상태(함수 컴포넌트에서 명확하게 '상태'라고 부를 수 있는지는 모르겠지만 아무튼 useState로 구현한 것)를 가지고 각 페이지를 이동시키고 있습니다.

  2. '카드 등록 페이지' (CardRegister 컴포넌트)의 각 input들은 실제 입력창이 여러 개인 경우가 있습니다. (카드 번호의 경우 4개, 만료일의 경우 month/year로 2개, 카드 비밀번호 각 1개) 그러나 마치 하나의 input처럼 보이기 위해, 각 input의 label과 회색 배경 등 시각적 요소들만 묶은 RegisterInputWrapper라는 컴포넌트와, 실제 입력이 이루어지는 input 컴포넌트들 (CardNumbersInput, ExpirationDateInput 등)이 분리되어 있습니다.

🤓 궁금한 점

Q1.
고민 내용 1번과 관련 있습니다. '카드 등록 페이지(form)'에서는 cardDatasetCardData를 props로 받아야 하고, '카드 등록 완료 페이지'는 그저 완성된 card만 보여주기 때문에 setCardData는 필요가 없습니다. 하지만 if 분기를 피하기 위해 PAGES라는 객체를 사용하고 있다보니 리턴되어 PageHostchildren으로 담기는 페이지가 모두 setCardData를 받고 있습니다. 처음부터 PAGES라는 객체의 키값으로 페이지를 구분해준 것이 역시 문제였을까요? 지금 상태에서 복잡한 if 분기 없이 각 페이지마다 필요한 props만을 넘겨줄 수 있는 방법은 없을지 궁금합니다!

Q2.
고민 내용 2번에 이은 질문입니다. 이렇게 하나의 input처럼 보여야 하는 영역이 실제로는 여러 input으로 구성되어 있을 경우, 리뷰어님이라면 어떻게 구현하셨을지 궁금합니다! 또 각 input에 입력되는 값을 분리하여 받기 위해 아래와 같이 useState를 구성하여, 임의의 key값을 부여한 객체 형태로 작성했습니다. state가 이렇게 복잡해지면 나중에 state를 update해줘야 할 곳에서 너무 많은 정보를 알아야 할 것이라고 생각하여 아주 좋은 코드는 아니라고 생각했지만, 다른 방법이 떠오르지 않아 이렇게 작성하게 되었습니다. 😵 state에 이런 식으로 객체를 부여하는 것도 자주 사용되는 패턴일까요?

// CardRegister.js
  const [cardNumbers, setCardNumbers] = useState({ [FIRST]: '', [SECOND]: '', [THIRD]: '', [FOURTH]: '' });
  const [expirationDate, setExpirationDate] = useState({ [MONTH]: '', [YEAR]: '' });

Q3.
cardNumbers, expirationDate와 같은 정보는 페이지 간 이동 시 넘겨줘야 하기 때문에 CardRegister가 갖고 있고, 실제로 사용되는 곳은 CardRegister의 자식인 CardRegisterForm을 거쳐 CardNumbersInput 등의 실제 input event가 이루어지는 컴포넌트입니다. 이렇게 두 차례에 걸쳐 props를 넘겨주는 것을 prop drilling이라고 하는 걸까요? 2단계에서는 Context API를 사용하여 중간에 단지 props들을 넘겨주는 역할만 하는 CardRegisterForm에 중복된 코드를 작성하지 않고 props들을 바로 자식의 자식 컴포넌트로 넘겨주려고 생각 중인데, 리뷰어님의 의견을 여쭤보고 싶습니다!

Q4.
아무래도 input이 많은 페이지다보니, 유효하지 않은 input에 대한 validation들이 필요합니다. 이번에는 따로 validation 파일을 분리하지 않고 필요한 각 컴포넌트에서 메소드를 만들어 사용했는데, 리뷰어님께서는 validation들을 어떻게 관리하시는지 궁금합니다. 각 validation들에 대한 설명은 코멘트로 간단히 달도록 하겠습니다. (현재 완벽하게 validation이 다 되어 있지 않습니다... 😭 저희가 인지하고 있는 부분은 계속 고쳐나가겠지만, 지적해주실 부분을 발견하신다면 알려주시면 감사하겠습니다.)

기타 자세한 코드에 대한 질문들 역시 코멘트로 남기도록 하겠습니다!

5/1 추가: 가상 키보드

image
React Portal로 화면 바깥쪽에 뜨도록 만들어보았습니다.
createPortalVirtualKeyboard를앱 전체의 바깥쪽에 뜨도록 하기 위해 사용해보았습니다.
잘못 작성된 부분이 있다면 평가 부탁드리겠습니다!

zigsong and others added 30 commits April 20, 2021 19:53
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
zigsong and others added 17 commits April 28, 2021 22:52
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
- 카드 비밀번호 인풋 width 삭제
- DateType 분리

Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
- 카드 번호, 비밀번호 fragment index 상수화

Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
- input에 aria-label 부여

Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
- 메소드 순서 변경

Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
src/App.js Show resolved Hide resolved
src/components/pages/CardList/index.js Show resolved Hide resolved
src/components/pages/CardRegister/index.js Show resolved Hide resolved
src/components/pages/CardRegister/index.js Show resolved Hide resolved
import React from 'react';
import * as Styled from './style';

const CardAddButton = () => {
Copy link
Author

Choose a reason for hiding this comment

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

2단계에서 사용할 컴포넌트입니다.

Comment on lines +46 to +47
min="1000"
max="9999"
Copy link
Author

Choose a reason for hiding this comment

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

카드번호 입력 폼에 0000~9999까지의 숫자만 입력이 가능하도록 하고 싶은데 (4자리),
지금처럼 minmax를 주면 사용자가 3자리만 입력하고 폼 제출 시도 시 "1000 이상으로 입력해주세요"와 같은 상황에 맞지 않는 메시지가 나옵니다. 물론 이 부분도 customValidity로 메시지를 바꾸고자 시도해보았지만, customValidity 메시지가 한번 뜨면 절대 사라지지 않아 일단 임시로 min, max만 주게 되었습니다.

input type이 number인 경우에는 minLength, maxLength를 줄 수가 없는데, 혹시 이렇게 들어와야 할 숫자의 자리수를 제한하고자 할 경우에는 어떻게 처리하시는지 궁금합니다!

Copy link

Choose a reason for hiding this comment

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

질문에 대한 답변을 드리자면, 제어 컴포넌트로 하여 이벤트 발생시 어떻게 처리 할지 결정합니다.(제한하고 입력을 막을지, 메시지를 노출할지, 제한치의 숫자로 변환 시킬지 등등)

근데 카드 번호 입력에서 0000 ~ 9999 까지의 숫자만 입력이 가능하도록 min 과 max를 정한다는게 다소 불필요한 일을 하는 것 같습니다.
카드번호의 숫자는 생긴게 숫자이지 숫자로써 쓰이지 않습니다.(산술 연산의 대상이 되지 않는 다는 의미)
현실세계에서 카드번호의 시스템이 그저 숫자일뿐이지, abce-ffaa-gwee-gkfk 이런식의 알파벳 체계로 카드번호가 현실세계에서 만약 쓰이고 있다고 a~z 까지 입력 값을 제한해야 하는것에 신경 쓸 필요가 없는것과 같습니다.
(실제 제한이 있는게 아닌이상)

Copy link
Author

@zigsong zigsong May 3, 2021

Choose a reason for hiding this comment

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

카드번호의 숫자는 생긴게 숫자이지 숫자로써 쓰이지 않습니다.(산술 연산의 대상이 되지 않는 다는 의미)

저도 이렇게 생각했는데 자릿수를 막을 방법이, 모든 input에 onChange 이벤트 핸들러를 붙여주는 것밖에 떠오르지 않아서 min max를 두었습니다. 카드번호는 프론트 단에서만 처리할 문제는 아니라는 점에는 동의하지만, 일단은 서버가 따로 없이 모든 카드번호가 4자리씩 채워져야지만 넘어갈 수 있도록 regex를 이용하여 작성해 보았습니다.

그리고 카드번호가 14자리가 min인지는 몰랐네요...!
이번 과제에서는 우선 4자리씩 총 16자리로 validation하는 정도로 구현해보겠습니당


return (
<RegisterInputWrapper type={type} label={label} width={width}>
<Style.InputWrapper>
Copy link
Author

Choose a reason for hiding this comment

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

이 안에 있는 4개의 NumberInput들도 array loop을 도는 게 좋을까요?
각각이 별개의 aria-labelvalue, data-number-idx, ref를 갖고 있어 이것들을 묶어준 후 loop을 도는 것이 더욱 복잡할 것 같아 일단 그대로 두었습니다.

Copy link

Choose a reason for hiding this comment

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

중복으로 보여서 loop 가 더 낫지 않을까 생각하셨을듯 합니다.
PAGES 객체 만드셨던 것처럼 이것도 각자 데이터들 갖도록 만들면 되기 때문에 묶는것은 문제가 아니나, 여기는 굳이 그렇게까지 할 필요는 없을 것 같습니다.

Comment on lines +27 to +31
if (index === FIRST) {
moveFocusToNextFragment();
} else if (index === SECOND) {
if (currentValue.length > 1) return;
}
Copy link
Author

Choose a reason for hiding this comment

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

카드 비밀번호의 첫 번째 칸을 입력하면 다음 칸으로,
그 다음 칸에 1자리 숫자를 입력하면 더 이상 입력할 수 없습니다

Comment on lines +20 to +41
const handleChangeDate = (event) => {
const dateType = event.target.dataset.dateType;
const value = event.target.value;

if (underTwoDigits(value)) {
setExpirationDate((prevDate) => ({ ...prevDate, [dateType]: value }));

return;
}

if (dateType === DATE_TYPE.MONTH) {
if (!isValidMonth(value)) return;

moveFocusToYearInput();
} else {
if (!isValidYear(value)) return;
}

if (overTwoDigits(value)) return;

setExpirationDate((prevDate) => ({ ...prevDate, [dateType]: value }));
};
Copy link
Author

Choose a reason for hiding this comment

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

TODO로 해놨지만 상당히 복잡한 부분입니드아...

month와 year를 각각 2자리로만 딱 입력하게 하기 위해서 1자리일 때(underTwoDigits), 2자리일 때, 3자리일 때 (overTwoDigits)를 나누게 되었습니다. input에 2자리 이상으로 입력이 되지 않게 하면서 분기를 개선할 수 있는 방법이 있을까요오... 코드가 썩 보기 좋진 않네요.... 흑

Copy link

Choose a reason for hiding this comment

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

월은 그렇다 치더라도 연도까지 밸리데이션 해야 하는 요구사항이 있어서 이렇게 구현을 하셨는지 잘 모르겠습니다만...
카드사의 년월이 유효한지 판단하는것은 자바스크립트로 클라이언트에서 해야 할 일이 우선 아닙니다.
유효한지는 카드사에서 알고 있기 때문에, 서버로 전달하고 서버가 카드사를 찔러서 확인하고 브라우저로 응답을 줘야 하는 형태로 되어야 하기 때문에 ...
그저 4개의 숫자가 입력이 되었는지, 앞 2자리는 01 ~ 12 내에 포함되어 있는지 정도만 확인해도 충분하지 않을까 싶네요.
충분히 간결하게 하실 수 있는 실력이 있으신데, 요구사항을 너무 복잡하게 설정하신 상태에서 벨리데이션과 포커스 이동과 입력값 세팅을 하나의 함수에 뒤섞어서 그런것 같습니다.

Comment on lines +17 to +21
if (!isEnglish(currentValue)) {
setInputValid(false);

return;
}
Copy link
Author

Choose a reason for hiding this comment

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

소유자 이름 입력란에는 영어만 입력할 수 있습니다.

zigsong and others added 5 commits April 30, 2021 15:09
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Co-authored-by: Lee Soo Yeon <2SOOY@users.noreply.github.com>
Copy link
Author

@zigsong zigsong left a comment

Choose a reason for hiding this comment

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

5/1 가상키보드 추가

public/index.html Show resolved Hide resolved
@wow9144
Copy link

wow9144 commented May 2, 2021

궁금한 점에 대한 답변
Q1.
코드에서 작성한 if 문(조건에 따른 분기)은 없앨 수 없습니다.
코드의 흐름을 바꾸기 위한(통제하기 위한) 요구에 의해(그것이 문제를 해결하기 위해 필수적) 등장했기 때문이죠.
그런 조건 분기를 보기 싫을 때 할 수 있는것은, 중첩된 분기를 중첩되지 않도록 풀어 쓰거나, 함수내에 있던 분기를 함수 바깥으로 옮기거나, 함수 바깥에 있던 분기를 함수내로 옮기거나 같은 식으로 그저 분기를 옮길뿐입니다.
그런 if문을 안쓸 수 있는 방법이 있다면 바로 지금 하신것처럼 대상을 추상화 하여 동일한 액션을 하는 형태로 인터페이스를 맞추는 방법이 유일합니다.
코드 내에서 REGISTER, LIST, COMPLETION을 각기 구분없이 currentPage 로 추상화하고 currentPage는 title: string, content: React.Component, backButton: boolean 의 프로퍼티를 갖는 인터페이스이고 currentPage그저 currentPage.content를 실행하는데, 이 때 content에 전달하는 객체는 handleGoNext, cardData, setCardData 프로퍼티를 갖는것으로 코드상에서 계약이 된 것이라고 보시면 됩니다.
이렇게 계약이 되었기에 if문을 제거 할 수 있었던것인데, 누군가는 이 계약을 깨야한다면 결국 그 누군가를 특정하기 위해 if문이 들어가는 수밖에 없습니다.

Q2.
일단 카드 번호에 한해서 이야기를 하면 first, second, third, fourth 와 같은 형태의 객체로 구현을 하지는 않았을 것 같습니다.
카드번호가 항상 4자리씩 16개의 숫자만 받을 수 있는게 아니고 카드의 번호는 14~16개의 숫자로 이뤄져있고 앞자리 몇 개 가지고 카드와 발급사 뭐 이런걸 구분 할 수가 있어서......
카드와 관련한 이런 비즈니스를 물어보신게 아니라 관리해야 하는 state가 복잡하고 많을 때 어떤 기준으로 어떻게 하느냐 하는게 질문이실 거 같은데 답변은 그 때 그 때 비즈니스에 따라 다르다라고 답변할 수 밖에 없을 것 같습니다.
state에 객체든 배열이든 얼마든지 들어갈 수 있습니다.

Q3. 말씀하신 그것이 Props Drilling 이 맞습니다. 이 단어에 대해 거부감을 느끼는 분들이 많은것 같은데 이 드릴링이 과도해서 중간에 몰라도 되는 컴포넌트들이 단순히 전달만을 위해서 계속 만들어져야 해서 관리 포인트가 늘어난다면 문제가 되지만 지금 이 정도는 문제가 없어보입니다.
도입을 해보시는것에 대해서 반대하지는 않지만, 추천하지도 않습니다. 지금의 요구사항에서는 오버엔지니어링으로써 복잡성을 늘리기만 할 것으로 생각됩니다.

Q4. 재사용 할 수 있는 형태의 밸리데이션이라면 별도로 분리하여 빼지만 재사용 할 수 있는 형태가 아니라면 분리해서 잡음으로 만드는것은 지양하는 편입니다. 다른 곳에서 필요한 정보가 아니라면 최대한 작은 구역에 격리시키는걸 선호합니다.(컴포넌트의 메서드도 괜찮다고 생각한다는 의미 입니다.)

Copy link

@wow9144 wow9144 left a comment

Choose a reason for hiding this comment

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

코드를 크게 바꿀 부분이 눈에 띄지는 않았습니다.
질문을 많이 주셨는데 대부분이 코멘트를 할 법한 부분에서 질문을 하셨더라능 :)
그런 부분들만 다음 단계에서 국소적으로 수정하시면 될 것으로 판단되어 바로 머지 하도록 하겠습니다.

public/index.html Show resolved Hide resolved

return (
<RegisterInputWrapper type={type} label={label} width={width}>
<Style.InputWrapper>
Copy link

Choose a reason for hiding this comment

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

중복으로 보여서 loop 가 더 낫지 않을까 생각하셨을듯 합니다.
PAGES 객체 만드셨던 것처럼 이것도 각자 데이터들 갖도록 만들면 되기 때문에 묶는것은 문제가 아니나, 여기는 굳이 그렇게까지 할 필요는 없을 것 같습니다.

import * as Style from './style';

const CardNumbersInput = (props) => {
const { type, label, width, cardNumbers, setCardNumbers } = props;
Copy link

Choose a reason for hiding this comment

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

setCardNumbers 보다는 onChange 느낌의 형태가 더 어울리지 않나 싶습니다.

이 컴포넌트는 입력된 카드번호를 콜백함수를 통해 외부에 전달하기만 할 뿐, 그 카드 번호를 set을 하는지 그 번호로 ajax통신을 하는지, 계산을 하는지는 이 컴포넌트의 관심사가 아니라 이 컴포넌트를 사용하는측의 관심사 입니다.
여기서 setCardNumbers 라는 이름으로 사용자측이 전달한 함수 이름과 똑같이 쓰게 되면 이 컴포넌트를 사용하는측에서 전달하는 함수가 set하지 않고 다른 로직의 함수로 변경되었을 때 이 곳도 이름을 똑같이 변경해줘야 하게 됩니다.

VirtualKeyboard와 비교해서 보시면 좋을 것 같습니다. VirtualKeyboard는 아주 잘 하셨습니다 👍

Comment on lines +20 to +41
const handleChangeDate = (event) => {
const dateType = event.target.dataset.dateType;
const value = event.target.value;

if (underTwoDigits(value)) {
setExpirationDate((prevDate) => ({ ...prevDate, [dateType]: value }));

return;
}

if (dateType === DATE_TYPE.MONTH) {
if (!isValidMonth(value)) return;

moveFocusToYearInput();
} else {
if (!isValidYear(value)) return;
}

if (overTwoDigits(value)) return;

setExpirationDate((prevDate) => ({ ...prevDate, [dateType]: value }));
};
Copy link

Choose a reason for hiding this comment

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

월은 그렇다 치더라도 연도까지 밸리데이션 해야 하는 요구사항이 있어서 이렇게 구현을 하셨는지 잘 모르겠습니다만...
카드사의 년월이 유효한지 판단하는것은 자바스크립트로 클라이언트에서 해야 할 일이 우선 아닙니다.
유효한지는 카드사에서 알고 있기 때문에, 서버로 전달하고 서버가 카드사를 찔러서 확인하고 브라우저로 응답을 줘야 하는 형태로 되어야 하기 때문에 ...
그저 4개의 숫자가 입력이 되었는지, 앞 2자리는 01 ~ 12 내에 포함되어 있는지 정도만 확인해도 충분하지 않을까 싶네요.
충분히 간결하게 하실 수 있는 실력이 있으신데, 요구사항을 너무 복잡하게 설정하신 상태에서 벨리데이션과 포커스 이동과 입력값 세팅을 하나의 함수에 뒤섞어서 그런것 같습니다.

@wow9144 wow9144 merged commit 59ccd70 into woowacourse:zigsong May 2, 2021
@zigsong
Copy link
Author

zigsong commented Jun 10, 2021

학습로그

블로그 링크

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.

2 participants