문제 :
모달을 띄워 여러 스텝으로 정보를 받아야하는 상황이 있었습니다. 기존 코드는 useModal 형태로 각 컴포넌트에서 Dialog의 state를 관리하는 방식으로 개발되어 있었습니다. 기존 방식으로 진행시 props의 복잡도와 어려운 사용성으로 팀 내에서 코드 개선을 요청하였습니다.
목표 :
누구나 쉽게 사용할 수 있는 사용성, 불필요한 재선언, 컴포넌트 간 props의 복잡도 개선을 목표로 합니다.
해결방법 :
리덕스 툴킷을 이용해 전역으로 isOpen 상태를 관리하고, GlobalModal을 만들어 보여주고자 한 Dialog를 type형태로 받아 Component 형태로 출력한다.
목차
1. redux/modalSlice.ts에 필요한 값 정의
2. GlobalModal.tsx 생성
3. App.tsx 모달창 호출
4. type별 필요한 Dialog 생성
5. 사용방법
type : 띄우고자 하는 Dialog
isOpen : Modal 열림/닫힘 상태
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
type ModalState = {
type: string;
isOpen: boolean;
};
const initialState: ModalState = {
type: '',
isOpen: false,
};
const modalSlice = createSlice({
name: 'modal',
initialState,
reducers: {
openModal: (state, action: PayloadAction<string>) => {
state.type = action.payload;
state.isOpen = true;
},
closeModal: (state) => {
state.isOpen = false;
},
},
});
export default modalSlice.reducer;
export const { openModal, closeModal } = modalSlice.actions;
모든 Modal을 관리할 수 있는 GlobalModal.tsx을 생성합니다. 이 파일에서는 사용하고자하는 Dialog를 type으로 받아 component 형태로 출력하고자 합니다.
import * as S from './Modal.styled';
import { Alert, Report } from '..';
import { useSelector } from 'react-redux';
import { RootState } from '../../redux/config/configStore';
import ModalPortal from './ModalPortal';
const MODAL_TYPES = {
basic: 'basic',
alert: 'alert',
};
const MODAL_COMPONENTS = [
{
type: MODAL_TYPES.basic,
component: <Report />,
},
{
type: MODAL_TYPES.alert,
component: <Alert />,
},
];
const GlobalModal = () => {
const { type, isOpen } = useSelector((state: RootState) => state.modal);
if (!isOpen) return;
const findModal = MODAL_COMPONENTS.find((modal) => {
return modal.type === type;
});
const renderModal = () => {
return findModal?.component;
};
return (
<ModalPortal>
<S.Overlay />
{renderModal()}
</ModalPortal>
);
};
export default GlobalModal;
장점 :
1. 필요한 모달을 다시 정의할 필요없이 App에서 사용할 수 있다.
2. 모달을 띄울 때 사용되는 scrim영역(overlay)을 중복해서 사용하지 않아도 된다.
<GlobalModal />을 모든 컴포넌트에서 렌더링 할 필요없이 App.tsx에서 렌더링합니다.
import GlobalModal from './components/modal/GlobalModal';
import Router from './shared/Router';
import GlobalStyle from './style/GlobalStyle';
function App() {
return (
<>
<Router />
<GlobalStyle />
<GlobalModal />
</>
);
}
export default App;
이후 dispatch를 통해 원하는 Dialog type을 호출해주면 원하는 정보를 띄어줄 수 있게 됩니다.
장점 :
1. 모든 컴포넌트에서 상태관리를 할 필요가 없다.
2. 하위 컴포넌트에서 모달창을 열고 닫는 Props의 복잡도가 해결된다.
서비스에서 사용할 Dialog 컴포넌트를 만들어서 사용합니다.
import { useDispatch } from 'react-redux';
import * as S from './Alert.styled';
import { closeModal, openModal } from '../../../redux/modules';
const Alert = () => {
const dispatch = useDispatch();
const handleClose = () => {
dispatch(closeModal());
};
return (
<S.Overlay>
<S.Container>
<S.Inner
onClick={(e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
}}
>
<button onClick={handleClose}>닫기</button>
</S.Inner>
</S.Container>
</S.Overlay>
);
};
export default Alert;
모달은 1가지 Dialog를 사용자에게 전달을 하기 때문에
리덕스에서 openModal(''), closeModal('') 2개의 dispatch 만으로도
멀티 스텝의 모달을 구현할 수 있습니다. (설문조사 형식으로 활용가능)
코드추가