ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 교보문고 상세 페이지 컴포넌트 분리 및 상태 관리 개선하기
    Next.js 2025. 4. 17. 00:05

    기존 상세 페이지는 레이아웃을 만들 때 재사용을 고려하지 않고 만들었다. 

     

    그래서 상세 페이지 코드만 1700줄까지 길어서, 하나의 컴포넌트에 하나의 기능만 관리를 할 수 있도록 분리를 했다. 

     

    어떻게 분리를 할지 교보문고 사이트에 들어가서 생각을 해봤는데, 시계 방향으로 설명을 하면, 

     

    • ReviewSecetion - 컨테이너 컴포넌트
    • ReviewHeader - 리뷰 작성글과, 리뷰 작성 버튼을 관리하는 컴포넌트
    • ReviewBox - 리뷰 작성을 한 후, 적용이 되는 컴포넌트
    • ReviewList, noReviews - 리뷰 작성한 걸 볼 수 있는 컴포넌트
    • ReviewModal - 리뷰 작성 누르면 나오는 컴포넌트

    위의 설명처럼 이런식으로 진행을 했다.

     

     

     

     

    기존에는 한 파일에서 관리를 하다 보니깐 너무 쉬웠는데, 분리를 하니깐 만들었던 기능도 안되서 힘들었다..

     

    제일  어렵고, 머리가 부숴지는 느낌이 들었던거는 상태 관리와 컴포넌트 분리, 상태 전달이였다.

     

    단방향 데이터 처음에는 어렵게 느껴졌지만, 하다 보니깐 익숙해져서 쉽게 느껴졌고, 에러가 나오면, 사용한 곳에서 문제가 발생한걸 쉽게 찾을 수 있어서 이 점은 좋았다. 

     

    분리를 하니깐, 1700줄 이였던 코드가 1150줄로 확 줄어들었다.

     

    `
    상세 페이지에서 리뷰 컨테이너 컴포넌트 호출 
    <ReviewSection
      reviewRef={eventRef.review}
      onRequireLogin={() => setShowLoginModal(true)}
    />
    
    export type ReviewType = {
        id: string;
        period: Date;
        content: string;
        tags: string[];
    }
    
    type ReviewSectionProps = {
        reviewRef: RefObject<HTMLDivElement> | null;
        onRequireLogin: () => void;
    }
    
    const ReviewSection = ({reviewRef, onRequireLogin}:ReviewSectionProps) => {
    
        const [showReviewModal, setShowReviewModal] = useState(false);
        const [reviews, setReviews] = useState<ReviewType[]>([]);
    
        const handleReviewSubmit = (newReview: ReviewType) => {
            setReviews(prev => [...prev, newReview]);
            setShowReviewModal(false);
        }
    
    
        return (
            <section className={styles.tab_content} style={{marginTop: "70px"}}>
                <div className={`${styles.mt_70}`}>
                    {/*!--> Klover 리뷰 제목*/}
                    <ReviewsHeader
                        reviewRef={reviewRef}
                        onOpenModal={() => setShowReviewModal(true)}
                        onRequiredLogin={onRequireLogin}
                        reviewCount={reviews.length}
                    />
    
    
                    {showReviewModal && (
                        <ReviewModal
                            onClose={() => setShowReviewModal(false)}
                            onSubmit={handleReviewSubmit}
    
                        />
                    )}
                    {/*!-- // Klover 리뷰 제목*/}
    
                    {/*!--> 사용자 총점 */}
                    <ReviewsBox/>
                    {/*나머지 리뷰박스, 리뷰리스트, 이런 곳에도 reviews={reviews} 값을 줘야한다.*/}
                    {/*!-- //사용자 총점 */}
    
                    {/*리뷰 적을 수 있는 칸 ..*/}
    
                    {reviews.length === 0 ? (
    
                    <NoReviews/> ) : (
                    <ReviewsList reviews={reviews}/>
    
                    )}
    
    
                </div>
                <div className={`${styles.product_detail_together}`}></div>
            </section>
        )
    }
    
    export default ReviewSection;
    
    `

     

     

    밑에는 공통 모달 컴포넌트를 한 번에 관리할 수 있게 해주는 컴포넌트인데 이걸 만든 이유는

    상세 페이지에서 모달 컴포넌트를 렌더링할 때 3~4개 씩 사용을 하다 보니깐, 그냥 하나의 컴포넌트에서 관리를 해서 하는게 좋을 것 같아서 만들어봤다. 

    `
    'use client'
    
    import GenericModal from "@/app/components/modals/GenericModal";
    import {useRouter} from 'next/navigation'
    
    type ModalManagerProps = {
        showCartModal: boolean;
        setShowCartModal: (value: boolean) => void;
    
        showLoginModal: boolean;
        setShowLoginModal: (value: boolean) => void;
    
    
        showAlreadyCart: boolean;
        setShowAlreadyCart: (value: boolean) => void;
    
        showWishLoginModal: boolean;
        setShowWishLoginModal: (value: boolean) => void;
    
    }
    
    export default function ModalManager({
                                             showCartModal,
                                             setShowCartModal,
                                             showLoginModal,
                                             setShowLoginModal,
                                             showAlreadyCart,
                                             setShowAlreadyCart,
                                             showWishLoginModal,
                                             setShowWishLoginModal
    
                                         }: ModalManagerProps) {
        const router = useRouter();
    
    
        return (
            <>
    
                {showWishLoginModal && (
                    <GenericModal
                        title="찜하기는 로그인 후 이용할 수 있어요"
                        confirmText="이동하기"
                        cancelText="취소"
                        onClose={() => setShowWishLoginModal(false)}
                        onConfirm={() => {
                            setShowWishLoginModal(false);
                            router.push("/login")
                        }}
                    />
                )}
    
    
                {showLoginModal && (
                    <GenericModal
                        title="로그인 후 이용가능합니다"
                        description="로그인 페이지로 이동하시겠습니까?"
                        confirmText="확인"
                        cancelText="취소"
                        onClose={() => setShowLoginModal(false)}
                        onConfirm={() => {
                            setShowLoginModal(false);
                            router.push("/login")
                        }}
                    />
                )}
    
                {showCartModal && (
                    <GenericModal
                        title="상품이 장바구니에 담겼습니다."
                        description="장바구니로 이동하시겠습니까?"
                        confirmText="확인"
                        cancelText="취소"
                        onClose={() => setShowCartModal(false)}
                        onConfirm={() => {
                            setShowCartModal(false);
                            router.push("/cart")
                        }}
                    />
                )}
    
                {showAlreadyCart && (
                    <GenericModal
                        title="장바구니에 이미 담은 상품이 있어 수량이 추가 되었습니다."
                        description="장바구니로 이동하시겠습니까?"
                        confirmText="확인"
                        cancelText="취소"
                        onClose={() => setShowAlreadyCart(false)}
                        onConfirm={() => {
                            setShowAlreadyCart(false);
                            router.push('/cart')
                        }}
                    />
                )}
                
            </>
        )
    }
    
    // 사용방법
    <ModalManager
     showCartModal={showCartModal}
     setShowCartModal={setShowCartModal}
     showLoginModal={showLoginModal}
     setShowLoginModal={setShowLoginModal}
     showAlreadyCart={showAlreadyCart}
     setShowAlreadyCart={setShowAlreadyCart}
     showWishLoginModal={showWishLoginModal}
     setShowWishLoginModal={setShowWishLoginModal}
                    />
    `

     

    src/
    ├── app/
    │   └── components/
    │       └── reviews/
    │           ├── NoReviews/
    │           │   └── NoReviews.module.css
    │           │   └── NoReviews.tsx
    │           ├── ReviewModal/
    │           │   └── ReviewModal.modue.css
    │           │   ├── ReviewModal.tsx
    │           ├── ReviewsBox/
    │           │   └── ReviewsBox.module.css
    │           │   └── ReviewsBox.tsx
    │           ├── ReviewsList/
    │           │   ├── ReviewsList.module.css
    │           │   ├── ReviewsList.tsx
    │           ├── ReviewsListHeader/
    │           │   └── ReviewsListHeader.module.css
    │           │   └── ReviewsListHeader.jsx
    │			└── ReviewSection.module.css
    │			└── ReviewSection.tsx

     

Designed by Tistory.