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기 정진범] 페이먼츠 미션 Step3 #159

Open
wants to merge 39 commits into
base: jungjinbeom
Choose a base branch
from

Conversation

jungjinbeom
Copy link

@jungjinbeom jungjinbeom commented Mar 20, 2024

안녕하세요 인성님!
PR 늦게 올려서 죄송합니다. 우선 최대한 개발해서 PR 올립니다.
나머지 개발되지 않은 부분은 7주차에 추가하려고 합니다.
지금은 피드백 주신 부분을 수정하면서 저번에 말씀해주신대로 두번 이상 핑퐁을 진행하고 싶습니다.

고민거리

  • 피드백 주신대로 CardListContext와 CardInfoContext를 합쳤는데 현재 작성한대로 관리를 하면 되는지 고민입니다
  • Context 리렌더링 이슈로 인한 성능저하를 우려해 메모제이션을 사용을 추천해주셨는데 Context 최상단에 선언하여 Props로 넘겨주고 Memo 처리해야하는지 아니면 Context에서 관리하는 함수를 메모제이션 훅으로 관리하면 되는지에 대해 고민이 됩니다.

필수 요구사항

  • 모바일 타겟의 웹 앱을 구현하며 사용하기 편리한 모바일 UI/UX에 대해 고민해봅니다.
  • Stepper 기반으로 작성한 애플리케이션을 �State Machine XState로 구현합니다.
  • 재사용 가능한 Component를 직접 작성하고 사용합니다.
  • Controlled & Uncontrolled Components에 입각하여 Form을 핸들링합니다.

UX/UI 요구사항

유효성 검증

  • 유효성 검증 실패에 대한 UI/UX 추가
  • 유효한 값 입력시 다음 필드로 Input Focusing

카드

  • 카드 번호 앞 8자리로 카드사를 추정하여 그 테마를 카드 UI에 반영한다.
  • 카드사를 선택하지 않아도 모달을 닫을 수 있다.
  • 카드사가 선택되고 유효한 카드 번호 16자리를 모두 입력하면, 자동으로 만료일로 focus된다.
  • 별칭 수정 가능

보안코드 툴팁

  • 클릭 시, 보안코드 관련 안내 메시지를 보여준다.
  • focusout 시, 툴팁이 닫힌다.

가상 키보드

  • 마스킹 처리된 값 입력시 사용
  • 숫자를 랜덤으로 배열

파일 구조 설정
스토리북 셋팅
카드 폼 컴포넌트 생성
요구사항 예외처리
카드폼 컴포넌트 개발
CardBox, CardForm 컴포넌트 구현
CardNumbers 실시간 UI 반영
카드 폼 제어컴포넌트로 수정
유효성 검사 함수 로직 수정
공통 컴포넌트 추가 및 수정
util 스타일 추가
Card 컴포넌트에 사용되는 UI 컴포넌트 수정
카드 리스트 Context 추가
디렉토리 구조 수정
사용하지 않는 상수 삭제
Card Chip 컴포넌트 추가
카드 상태 초기화 함수 추가
사용하지 않는 스타일 삭제
컴포넌트 타입추가
카드 컴포넌트 필요한 컴포넌트 파일 위치 이동
모달에 사용되는 컴포넌트 추가
ModalContext 추가
모달 컴포넌트 개발
Input 포커싱 기능 추가
유효성 검증 기능 추가
모달 Context 추가
카드 선택시 카드사 선택 기능 추가
카드 필드 상수값 위치 및 상수명 수정
useContext 커스텀훅으로 변경
Copy link
Member

@InSeong-So InSeong-So left a comment

Choose a reason for hiding this comment

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

안녕하세요, 진범님!
코드 변경사항 81개... 바쁘실 텐데 수고 많으셨습니다 👍👍👍

피드백 주신대로 CardListContext와 CardInfoContext를 합쳤는데 현재 작성한대로 관리를 하면 되는지 고민입니다

  • 합쳤기 때문에 관심사가 명확히 응집되었는데, 해당 컨텍스트의 역할이 너무 거대해보이긴 합니다.
  • 이 시점에서 CardInfoContext를 사용하는 커스텀 훅을 만들어서 제어하는 형태로 리팩토링하면 어떨까요? Context 제공 주체는 CardInfoContext, 그것을 사용하기 좋게 만들어둔 Custom Hook. 이러면 사용자는 훅만 잘 가져다 사용하면 될 거 같거든요!

Context 리렌더링 이슈로 인한 성능저하를 우려해 메모제이션을 사용을 추천해주셨는데 Context 최상단에 선언하여 Props로 넘겨주고 Memo 처리해야하는지 아니면 Context에서 관리하는 함수를 메모제이션 훅으로 관리하면 되는지에 대해 고민이 됩니다.

  • 전자도 좋고, 후자도 좋습니다. 어느 정도의 Props나 함수가 메모이제이션 될 지를 염두에 두고 개발하신다면 엄청 효율적으로 리팩토링하실 수 있을 거에요.
  • 사실 그만큼 성능 저하가 있는 것도 아닌데! 라는 생각이 드시더라도 학습을 위해서 메모이제이션을 적용하고 어느 부분에서 리렌더링이 방지되고 불필요한지 리액트 개발 툴로 확인해보면 좋을 것 같아요.

이번에는 RC 드릴게요. 내용 읽어보시고 하나씩 수정해보세요!

Comment on lines 17 to 24
onLogin={() => {
setUser({name: 'Jane Doe'});
}}
onLogout={() => {
setUser(undefined);
}}
onCreateAccount={() => {
setUser({name: 'Jane Doe'});
Copy link
Member

Choose a reason for hiding this comment

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

src/stories는 사용하지 않는 코드니까 제거해도 좋습니다~!

Comment on lines 7 to 9
return null;
throw new Error('라우터 컨텍스트 입니다!');
}

Copy link
Member

Choose a reason for hiding this comment

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

👍
그런데 에러 문구가 조금 난해하네요. 해당 컨텍스트는 StepProdiver 하위에서만 사용해주세요 등은 어떨까요?
에러 문구를 보고 어떻게 수정하면 될 지 알려주는 방향이 더 좋아보입니다!

Comment on lines 18 to 20
const navigate = (path: Route) => {
setRoute(path);
};
Copy link
Member

Choose a reason for hiding this comment

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

route, navigate는 memoization 하여 잦은 리렌더링을 방지하는 하나의 대책으로도 쓸 수 있을 것 같네요!

.eslintrc.js Outdated
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
// 'plugin:react-hooks/recommended',
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
Author

Choose a reason for hiding this comment

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

사용하지 않는 룰인데 주석 처리하고 삭제하는걸 깜빡햇습니다

</CardInfoProvider>
</>
)}
<Stepper />
Copy link
Member

Choose a reason for hiding this comment

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

훨씬 선언적으로 되었네요 👍

Comment on lines 11 to 15
const Input = forwardRef(
(
{ type = 'text', boxType = 'input-basic', className, ...props }: InputProps,
ref: ForwardedRef<HTMLInputElement>,
) => {
return <input type={type} className={classNames(boxType, className)} {...props} ref={ref} />;
},
) => <input type={type} className={classNames(boxType, className)} {...props} ref={ref} />,
Copy link
Member

Choose a reason for hiding this comment

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

forwardRef는 제네릭 타입을 지원해서 첫 번째, 두 번째 인자를 잘 정의해주면 ForwardedRef 를 추가적으로 import 하지 않아도 될 것 같아요. 의도한 타입일까요?

Copy link
Author

Choose a reason for hiding this comment

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

주로 사용하던 방법인데 굳이 이렇게 사용하지 않아도 말씀해주신 forwardRef 제네릭 타입을 활용하는게 좋은거 같다고 생각합니다

Comment on lines 7 to 19
const Modal = ({ onToggle, isOpen, children }: ModalProps) =>
isOpen ? (
<div className="modal-dimmed" onClick={onToggle}>
<div
className="modal"
onClick={(e) => {
e.stopPropagation();
}}
>
{children}
</div>
</div>
);
};
) : null;
Copy link
Member

Choose a reason for hiding this comment

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

createPortal을 적용해보면 어떨까요?!

@@ -1,4 +1,10 @@
export const MIN_MONTH = 1;
export const MIN_MONTH = 0;
Copy link
Member

Choose a reason for hiding this comment

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

상수를 작성할 때도 고민해보면 좋을 부분이, MONTH는 1~12인데 0이라고? 라는 생각을 제 3자가 가질 수 있다는 겁니다.
어떻게 처리하면 좋을까요?

export const validNumber = (value: string) => {
return /^(?:[1-9][0-9]{0,2}|)$/.test(value);
};
export const validNumber = (value: string) => /^(?:[1-9][0-9]{0,2}|)$/.test(value);
Copy link
Member

Choose a reason for hiding this comment

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

이 함수도 boolean을 반환하므로 아래의 네이밍처럼 일관성을 만들어주면 어떨까요?

Suggested change
export const validNumber = (value: string) => /^(?:[1-9][0-9]{0,2}|)$/.test(value);
export const isValidNumber = (value: string) => /^(?:[1-9][0-9]{0,2}|)$/.test(value);

Comment on lines 5 to 29
const Stepper = () => {
const { route } = useStepContext();

if (route === 'LIST') {
return <CardList />;
}

if (route === 'CARD') {
return (
<ModalProvider>
<AddCard />
</ModalProvider>
);
}

if (route === 'COMPLETE') {
return (
<ModalProvider>
<CardRegisterComplete />
</ModalProvider>
);
}

return <></>;
};
Copy link
Member

Choose a reason for hiding this comment

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

이 시점에서 이러한 코드를 깔끔하게 정리하는 좋은 코드 기법을 알려드리자면,

Suggested change
const Stepper = () => {
const { route } = useStepContext();
if (route === 'LIST') {
return <CardList />;
}
if (route === 'CARD') {
return (
<ModalProvider>
<AddCard />
</ModalProvider>
);
}
if (route === 'COMPLETE') {
return (
<ModalProvider>
<CardRegisterComplete />
</ModalProvider>
);
}
return <></>;
};
const StepperContent = {
LIST: <CardList />,
CARD: (
<ModalProvider>
<AddCard />
</ModalProvider>
),
COMPLETE: (
<ModalProvider>
<CardRegisterComplete />
</ModalProvider>
),
} as const;
const Stepper = () => {
const { route } = useStepContext();
const Component = StepperContent[route] ?? null;
return Component;
};

이렇게 key-value 쌍으로 만들 수 있답니다!

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