Closed hakyoung12 closed 3 weeks ago
전반적으로 Bottom Floating Bar 컴포넌트에 사용되는 ParticipationButton 컴포넌트의 가독성이 좋지 않습니다. 어떻게 하면 조금 더 가독성있게 만들 수 있을까요?
가장 쉽게 예를 들면 아래와 같이 switch case 문을 사용해볼 것 같아요.
'use client';
import Button from '../Button/Button';
import { onCancel, onShare, onJoin, onWithdraw } from './Mock';
interface Participant {
User: {
id: number;
};
}
// @todo api 연결 후 Props 수정
interface ParticipationButtonProps {
isHost: boolean;
user: { name: string; id: number };
participantCount: number;
capacity: number;
registrationEnd: string;
canceledAt: null | string;
participantsData: Participant[];
}
const ParticipationButton = ({
user,
isHost,
participantCount,
capacity,
registrationEnd,
canceledAt,
participantsData,
}: ParticipationButtonProps) => {
const getButtonProps = (): { name: string; variant: 'white' | 'default' | 'gray'; action?: () => void; disabled?: boolean } => {
const isFull = participantCount === capacity;
const isRegistrationEnded = new Date() > new Date(registrationEnd);
const hasParticipated = participantsData.some(
(participant) => participant.User.id === user.id
);
const isCancelled = Boolean(canceledAt);
const isParticipationDisabled = isFull || isRegistrationEnded || isCancelled;
switch (true) {
case isHost:
return {
name: '취소하기',
variant: 'white',
action: onCancel,
disabled: isRegistrationEnded || isCancelled,
};
case hasParticipated:
return {
name: '참여 취소하기',
variant: 'white',
action: onWithdraw,
};
case isParticipationDisabled:
return {
name: '참여하기',
variant: 'gray',
action: undefined,
disabled: true,
};
default:
return {
name: '참여하기',
variant: 'default',
action: onJoin,
};
}
};
const { name, variant, action, disabled } = getButtonProps();
return (
<div className={`h-44 w-[115px] ${isHost && 'w-full md:w-[115px]'}`}>
<Button
name={name}
type="button"
onClick={action}
variant={variant}
disabled={disabled}
/>
</div>
);
};
export default ParticipationButton;
조금 더 나아가서 타입스크립트를 사용한다면 ts-pattern을 사용할 수 있는데요,
ts-pattern을 사용하면 switch 문을 더 선언적으로 작성하고, TypeScript 타입 시스템을 더 강력하게 활용할 수 있습니다.
'use client';
import Button from '../Button/Button';
import { onCancel, onShare, onJoin, onWithdraw } from './Mock';
import { match } from 'ts-pattern';
interface Participant {
User: {
id: number;
};
}
// @todo api 연결 후 Props 수정
interface ParticipationButtonProps {
isHost: boolean;
user: { name: string; id: number };
participantCount: number;
capacity: number;
registrationEnd: string;
canceledAt: null | string;
participantsData: Participant[];
}
const ParticipationButton = ({
user,
isHost,
participantCount,
capacity,
registrationEnd,
canceledAt,
participantsData,
}: ParticipationButtonProps) => {
const isFull = participantCount === capacity;
const isRegistrationEnded = new Date() > new Date(registrationEnd);
const hasParticipated = participantsData.some(
(participant) => participant.User.id === user.id
);
const isCancelled = Boolean(canceledAt);
const isParticipationDisabled = isFull || isRegistrationEnded || isCancelled;
const { name, variant, action, disabled } = match({ isHost, hasParticipated, isParticipationDisabled })
.with({ isHost: true }, () => ({
name: '취소하기',
variant: 'white' as const,
action: onCancel,
disabled: isRegistrationEnded || isCancelled,
}))
.with({ hasParticipated: true }, () => ({
name: '참여 취소하기',
variant: 'white' as const,
action: onWithdraw,
}))
.with({ isParticipationDisabled: true }, () => ({
name: '참여하기',
variant: 'gray' as const,
action: undefined,
disabled: true,
}))
.otherwise(() => ({
name: '참여하기',
variant: 'default' as const,
action: onJoin,
}));
return (
<div className={`h-44 w-[115px] ${isHost && 'w-full md:w-[115px]'}`}>
<Button
name={name}
type="button"
onClick={action}
variant={variant}
disabled={disabled}
/>
</div>
);
};
export default ParticipationButton;
리팩토링 방향 : 조금 더 선언적으로 컴포넌트 작성하기
✏️ 작업 내용
-Bottom Floating Bar 컴포넌트 UI 작성
📷 스크린샷
참가자 UI
PC화면
태블릿 화면
모바일 화면
참여 불가능할 때
주최자 UI
PC화면
태블릿 화면
모바일 화면
✍️ 사용법
위와 같이 props를 내려주어 사용할 수 있습니다.
🎸 기타
실제 api 결과에 따라 props를 변경할 가능성이 있습니다.