저의 모바일 청첩장 개발 후, 감사하게도 스몰 빅웨딩 모바일 청첩장 외주를 맡아 개발 중입니다. 1차 개발 완료 후, 지속적으로 주문이 들어오고 있어요.
다만 1인 개발이다보니, 리뷰를 받지 못하고 머지해버린 코드들이 항상 마음에 걸렸습니다.
미뤄왔던 리팩토링을 해나가는 과정을 하나씩 올려볼까 합니다.
제가 생각하는 리팩토링 꼭지는 다음과 같습니다.
- 구조 변경
- 관심사 분리
- 스타일링 깔끔하게 관리하기
작게라도 빠르게 시작할 수 있는 2번을 먼저 진행하기로 했습니다.
결국은 관심사 분리를 해야 파일이 나눠지고 결과적으론 구조 변경도 더 쉬워질 거라고 생각해서요!
제가 개발한 갤러리의 요구사항은 아래와 같습니다.
코드를 보여드릴 수 없지만 요구사항을 만족하기 위한 기능들이 한 파일에 들어 있었습니다.
function Slider() {
// 갤러리에 쓰일 사진을 불러내기
...
// 슬라이드와 관련된 코드들
const nextSlide = () => {
...
};
const prevSlide = () => {
...
};
// 모달과 관련된 코드들
const [isModalOpen, setIsModalOpen] = useState(false);
...
// 터치 슬라이드와 관련된 코드들
...
return (
<Container>
<SliderContainer ref={slideRef} currentSlide={currentSlide}>
{imageUrls.map((img, i) => (
...
))}
</SliderContainer>
{isModalOpen && (
<>
<Modal show={isModalOpen}>
<ModalSliderWrapper>
<SliderContainer
ref={modalRef}
...
</SliderContainer>
</ModalSliderWrapper>
<CloseButton onClick={closeModal}>×</CloseButton>
<PrevButton onClick={prevSlide}>‹</PrevButton>
<NextButton onClick={nextSlide}>›</NextButton>
</Modal>
</>
)}
<ArrowWrapper>
<Button onClick={prevSlide}>
<ArrowLeft />
</Button>
<div>
{currentSlide + 1}/{photoNum}
</div>
<Button onClick={nextSlide}>
<ArrowRight />
</Button>
</ArrowWrapper>
</Container>
);
}
export default Slider;
이 기능들을 분리하기로 했습니다.
1, 3번에서 클릭으로 사진 슬라이드가 되도록 하는 기능이 중복되었기 때문에 이 부분은 훅으로,
2번의 모달이 열려있는지 여부를 다른 컴포넌트에서 필요로 하기 때문에 프로바이더로 뺀다.
ModalContext와 ModalProvider를 만들고, 기존의 코드에선 상태를 얻을 수 있도록 수정했습니다.
import React, { createContext, ReactNode, useContext, useState } from 'react';
interface ModalContextType {
isModalOpen: boolean;
openModal: () => void;
closeModal: () => void;
}
const ModalContext = createContext<ModalContextType | undefined>(undefined);
export const ModalProvider = ({ children }: { children: ReactNode }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
return (
<ModalContext.Provider value={{ isModalOpen, openModal, closeModal }}>
{children}
</ModalContext.Provider>
);
};
export const useModalContext = () => {
const context = useContext(ModalContext);
if (!context) {
throw new Error('useModalContext must be used within a ModalProvider');
}
return context;
};
슬라이드와 관련된 코드들을 분리했습니다.
export const useSlider = () => {
// 갤러리에 쓰일 사진을 불러내기
...
// 슬라이드와 관련된 코드들
const nextSlide = () => {
...
};
const prevSlide = () => {
...
};
return {
currentSlide,
setCurrentSlide,
imageUrls,
photoNum,
nextSlide,
prevSlide,
};
};
...
import { useModalContext } from '@contexts/modalContext';
import { useSlider } from '@hooks/useSlider';
...
function Slider() {
...
const { isModalOpen, openModal, closeModal } = useModalContext();
const {
currentSlide,
imageUrls,
photoNum,
nextSlide,
prevSlide,
isPrevInvisible,
isNextInvisible,
} = useSlider();
...
useEffect(() => {
...
}, [isModalOpen]);
return (
...
);
}
export default Slider;
한개의 파일에 얽혀있던 코드가 관심사에 따라 ModalContext.tsx, ModalProvider.tsx, Slider.tsx, useSlider.ts로 쪼개지면서 (조금이나마) 유지보수에 용이해졌습니다.
완벽하게 정리되진 않았지만, 뷰에 충실한 컴포넌트로 1차 정리함으로써 마음이 조금 편안해졌네요.
추후에 여력이 된다면 갤러리 기능을 잘 분리해서 라이브러리로 만들어보면 좋겠습니다. :D