이 프로젝트에서는 페이지의 구조와 컴포넌트들을 개선하기 위해 관심사 분리와 컴포넌트 모듈화를 적용하였다.
기존 코드에서는 여러 데이터가 하나의 컴포넌트에 집중되어 있어
가독성이 떨어지고 유지보수가 어려운 문제가 존재하였다.
이를 해결하기 위해 컴포넌트의 역할을 분리하고, 데이터와 UI 로직을 명확히 구분하는 방향으로 리팩토링을 진행하였다
기존 코드에서는 각기 다른 화면 크기(PC, 모바일 등)와 관련된 로직, 여러 종류의 배너와 리스트 출력이 하나의 컴포넌트 안에 몰려 있어 가독성이 떨어졌으며 또한, 화면 크기에 따라 출력되는 내용이 달라지는 조건문들이 복잡하게 얽혀 있었고, 그로 인해 리렌더링이 잦은 문제가 발생하였다
기존 코드 예시
return (
<SMainSlideContent>
{data.map => {
if (data === 0) return null;
return (
<React.Fragment>
<div>
{windowSize > 1024 ? (
<MainSlideSectionPC />
) : (
<MainSlideSectionMobile />
)}
</div>
{index === 0 && (
<>
<MainSlideSolutionSection />
<section
onClick={() => handleFileListClick({ ... })}
>
<img alt="업종별 계약서 자료" src="..." />
</section>
<MainCuration />
{/* ... 기타 코드 ... */}
</>
)}
</React.Fragment>
);
})}
</SMainSlideContent>
);
리팩토링 후 개선된 코드
const MainSlideContent = () => {
const windowSize = useReactiveVar(windowSizeVar);
const 카테고리목록 = data.카테고리;
if (error) return <ErrorBox error={error} />;
if (loading) return <MainCategorySkeleton />;
if (!카테고리목록 || 카테고리목록.length <= 0) return <></>;
return (
<SMainSlideContent>
{카테고리목록.map((카테고리, 순번: number) =>
windowSize > parseInt(theme.breakpoints.tablet) ? (
<MainSlideSectionPC categoryItems={카테고리} index={순번} />
) : (
<MainSlideSectionMobile categoryItems={카테고리} index={순번} />
)
)}
</SMainSlideContent>
);
};
PC와 모바일 컴포넌트를 화면 크기에 맞춰 분리하여 관리하고, 불필요한 리렌더링을 방지할 수 있게 개선하였다.
기존 코드에서는 삼항 연산자를 사용하여 가격 처리 로직을 다루었으나, 이로 인해 코드가 복잡하고 가독성이 떨어졌다. 이를 해결하기 위해 별도의 함수를 만들어 가격을 계산하고 결과만 반환하도록 하였다.
<ul>
{데이터.구매_데이터.map(({ 아이디, 제목, 멤버십_가격, 원가, 할인가격, 썸네일 }: any, index: number) => {
return (
<li key={`${아이디}-${제목}`} onClick={() => handleFileListClick({...})}>
<section>
<span>{index + 1}</span>
{isSubscription && 멤버십_가격 ? (
<div>
{멤버십_가격 !== 0 && 멤버십_가격 < 원가 && (
<span>{Math.floor(((원가 - 멤버십_가격) / 원가) * 100)}%</span>
)}
<span>{(멤버십_가격)}원</span>
</div>
) : 할인가격 ? (
<div>
{할인가격 !== 0 && 할인가격 < 원가 && (
<span>{Math.floor(((원가 - 할인가격) / 원가) * 100)}%</span>
)}
<span>{(할인가격)}원</span>
</div>
) : (
<div><span>{(원가)}원</span></div>
)}
<p>{제목}</p>
</section>
<section>
<img src={`${썸네일}`} style={{ objectFit: hover === index ? 'contain' : 'none' }} />
</section>
</li>
);
})}
</ul>
개선된 코드이다
export default function MainBestStoreList({ topProductsData }: MainBestVideoClassListProps) {
const products = topProductsData?.get_top_purchased_products.topPurchasedProducts;
const sliceProducts = products && products.slice(0, 5);
const [hover, setHover] = useState<number | null>(null);
const isSubscription = useReactiveVar(isSubscriptionVar);
const handleCreateMainClickLog = useCreateMainClickLog();
return (
<ul>
{상위5개상품?.map((상품, 순번: number) => {
const { 최종가격, 할인율 } = getDiscountedPrice({
isSubscription: 구독사용중,
membershipPR: 상품.membership_price,
discountPR: 상품.dc_price,
originalPR: 상품.org_price
});
return (
<li
onMouseOver={() => 호버설정(순번)}
onMouseOut={() => 호버설정(null)}
key={`${상품.seq}-${상품.title}`}
onClick={() => 클릭로그생성({...})}
>
<section>
<span>{순번 + 1}</span>
{할인율 !== null && <span>{할인율}%</span>}
<span>{number_price_to_string_comma_price(최종가격)}원</span>
<p>{상품.title}</p>
</section>
<section>
<img
style={{ objectFit: 호버된상품 === 순번 ? 'contain' : 'none' }}
src={`${CONFIG.IMG_URL}${상품.thumb}`}
alt={상품.title}
/>
</section>
</li>
);
})}
</ul>
);
}
getDiscountedPrice 함수로 가격 처리 로직을 분리하여, 가독성과 유지보수성을 개선하였다.
리팩토링 후, 각 컴포넌트는 단일 책임을 가지게 되었으며, 이는 코드의 가독성 및 유지보수성을 크게 향상시켰습니다. 각 부분을 독립적으로 관리하고, 수정 시 다른 부분에 미치는 영향을 최소화할 수 있게 되었습니다.
이번 리팩토링은 코드의 유지보수성과 가독성을 개선하는 데 중점을 두었으며, 특히 성능 최적화와 구조화된 컴포넌트를 통해 코드가 더 명확하고 간결해졌습니다. 각 컴포넌트가 담당하는 역할이 분리되어 향후 코드 확장이 용이하고, 필요한 부분만 빠르게 수정할 수 있게 되었습니다.