[react] 리액트 모달 리팩토링 과정

개굴맹꽁·2024년 3월 10일
2

개요

프로젝트 리팩토링 중 모달 부분을 리팩토링 했던 과정입니다.

리팩토링 전 상태

//page.jsx
export default function Page(){
	const [isOpen, setIsOpen]=useState(false);
	const handleOpenModal=()=>{setIsOpen(true)}
  return(
    <div>
      	{isOpen && <Modal>모달이징</Modal>}
    </div>
  )}

기존에는 이렇게 isOpen의 값을 변경해서 모달을 열고 닫는 방식으로 페이지에서 모달을 관리했습니다. 하지만 이 방식은 모달이 1~2개 정도까지는 괜찮은데 모달이 여러개가 되면 관리해야 되는 상태도 많아지고 상태 변경 함수도 늘어나면서 관리가 어려웠습니다.

기존 페이지에서의 모달 상태들 현재 리팩토링을 해야되는 페이지의 모달 관련 상태와 관련 함수들(열고, 닫는) 이다. 모달은 4개이고 ,함수는 각 2개씩 8개, 총 12개이다.

리팩토링

1. 관련된 상태를 객체로 묶어서 하나로 관리

우선 처음에 진행했던 방식은 관련된 상태들을 객체로 묶어서 하나로 관리하는 방법을 적용했다.

위의 모달 관련 상태를 객체로 변경 이제 모달관련은 모달 상태 객체, 모달 상태 객체 변경 함수만 남았다.

이 상태에서 모달을 열고 닫을 때는 객체에서 해당 모달 상태만 변경해주면 됩니다.

이런식으로 모달을 열고 닫을 때 해당하는 모달 상태를 변경하면 된다. 하지만 이 방식은 이전 상태 객체를 분해, 해당 모달 상태만 변경하는 부분이 반복적이고 이 프로젝트에서는 타입스크립트를 사용하고 있어서 오타를 막을 수 있었지만 만약 타입스크립트가 없다면 해당 과정에서 오타가 생길 수 있다.

2. useReducer 적용

위의 문제 해결을 위해서 useReducer를 적용해서 상태 변경 과정에서 반복적이고, 길어지는 코드를 사용전에 정의해주고, 사용할 때는 간단하게 사용할 수 있다.

각 모달을 열고, 모달을 닫는 상태를 useReducer로 정의

모달 상태 객체와 상태변경을 위한 dispathcer reduer의 액션 타입 정의 모달 상태 변경시 직접 객체 상태를 변경하는 것이 아니라, dispathcher에 정의 해두었던 액션을 넣어주면 된다. 만약 타입스크립크를 사용하는 환경이 아니라면 action객체를 만들어서 사용한다면 오타 문제를 해결 할 수 있을것이다.
//사용할 액션을 객체로 만들어둔다. 
	const action={
		close:'close',
		openWithdrawal:'openWidthdrawalModal',
		openEmail:'openEmailModal'
	}

	dispatchModalState(action.openEmail); 

이렇게 reducer로 상태변경을 미리 정의를 해두니 사용할때는 간단했지만 page에서 모달 관리를 위해서 기존 보다 더 많은 코드를 작성해야 하는 것 같았다. 그리고 이 페이지에서는 모달 상태가 4개라서 적용하는게 더 이득이였지만 1~2개와 같이 모달 수가 적다면 페이지마다 모달 관리를 위해 객체를 만들고 reducer를 만드는 것은 불필요해 보였다.

3. 모달 관련을 한 컴포넌트에서 관리

위와 같은 방식들은 모달을 사용하는 page에서 매번 관련 상태를 만들어줘야한다. 이 부분을 해결하기 위해 모달 관련 컴포넌트를 만들어서 상태를 컴포넌트 내에서 관리하도록 했다. 이것으로 페이지에서는 모달 상태를 관리 할 필요가 없어졌다.

모달을 한 컴포넌트에서 관리
//Page.jsx
export default function Page(){

  return (
   <ModalTriggerButton
   	ModalContent={
   		<ModalBasic onClickYes={handleDeletePlan} onClickNo={onClickNo} confirmSentense="삭제 하기">
          정말 해당 계획을 삭제하시겠습니까 ?
    	</ModalBasic>}>
 		<span>삭제</span>
 	</ModalTriggerButton>
  )

}

실제 페이지에서는 이런식으로 사용할 수 있다. 이런 방식으로 구현을 하니 페이지에서는 모달 관련 상태를 선언할 필요가 없고 컴포넌트 하나에 해당 모달과 관련된 내용을 모아 놓아서 어떤 내용의 모달이 나올지 알 수 있지만 코드를 보면 가독성이 떨어진다. 컴포넌트 이름과 안 맞게 Trigger 역할만 하는 것이 아니라 모달 내용도 받아서 띄워주고 있고, 어떤 부분이 trigger 역할을 하는 지도 알아보기 어렵다.

모달위치 수정이 필요하다. 그리고 이 방식은 모달이 띄워주는 부분에 문제가 있다. 리액트에서는 이 문제에 대해서 createPortal을 이용하면 해결 할 수 있다. createPortal에 모달이 생성될 위치도 전달 원하던 위치에 생성된 모달

4. 역할에 따라서 컴포넌트 분리

관리를 편하게 하기 위해서 컴폰넌트 하나에 묶었지만 컴포넌트 하나가 너무 많은 역할을 하는 것 같았고 분리가 필요하다고 생각했다. 그래서 compund 패턴을 적용해서 역할에 따라서 컴포넌트의 분리를 진행했다.

이 컴포넌트을 보면 컴포넌트 내에서 상태를 공유를 위한 context, 그리고 Provider역할을 위한 Main 컴포넌트, 모달을 열고 닫는 trigger역할을 할 Trigger 컴포넌트, 모달 내용을 받아줄 ModalContent 컴포넌트가 있다.

Page.jsx에 실제 적용한 모습

위 컴포넌트 형태는 compound 패턴을 적용해서 각 역할을 분리했습니다. 이런 식으로 만드니 위와 같이 관련 컴포넌트를 한번에 볼 수 있지만 어떤 역할을 하는지 확실하게 분리를 할 수 있었다.

5. popoverAPI(예정)

모달과 관련해서 2023년에 popoverAPI가 나왔는데 이것을 실제 적용해보고 싶었지만 프로젝트에 적용하기에는 아직 이른 것 같아서 나중으로 미루었다.

popoverAPI로 만든 모달 컴포넌트

popoverAPI를 적용하면 기존에 개발자가 작성해야 했던 코드에 대해서 많은 부분을 제거할 수 있을것 같다.
팝오버 소개
mdn에도 설명이 있지만 더 잘 설명해주는 블로그 글이 있다.
그리고 아직 적용이 안되는 브라우저가 많아서 polyfill적용이 필요하다.
popover-polyfill

1개의 댓글

comment-user-thumbnail
2024년 3월 26일

리팩토링 야무지네요 👍🏻

답글 달기