React TS: 모달 구성 요소를 관리하는 방법(Custom Modal Hook)
91252 단어 beginnerstypescriptreactwebdev
How I manage modal components (Custom Modal Hook)
제목과 내용 입력 필드가 있는 모달이 있다고 가정해 보겠습니다. 아래와 같이 모달을 구현할 수 있습니다.
UI에는
MUI
를 사용했습니다.import { useForm } from 'react-hook-form';
import Box from '@mui/material/Box';
import Modal from '@mui/material/Modal';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import { IModal } from '../../../types/modal';
export interface PostUploadModalProps extends IModal {
onSubmit?: (title: string, content: string) => void;
}
const style = {
position: 'absolute' as 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '1px solid #000',
boxShadow: 24,
p: 4,
};
const PostUploadModal = ({
visible = false,
onClose,
onSubmit,
}: PostUploadModalProps) => {
const { register, handleSubmit: handleFormSubmit } = useForm<{
title: string;
content: string;
}>();
const handleSubmit: Parameters<typeof handleFormSubmit>[0] = (values) => {
onSubmit?.(values.title, values.content);
onClose?.();
};
return (
<Modal open={visible} onClose={onClose}>
<Box sx={style}>
<TextField
{...register('title', { required: true })}
sx={{ width: '100%', marginBottom: 2 }}
label="Title"
placeholder="Enter the title"
/>
<TextField
{...register('content', { required: true })}
sx={{ width: '100%', marginBottom: 2 }}
label="Content"
multiline
rows={4}
placeholder="Enter the content"
/>
<Grid container justifyContent="flex-end">
<Button
variant="contained"
color="success"
onClick={handleFormSubmit(handleSubmit)}
>
Submit
</Button>
</Grid>
</Box>
</Modal>
);
};
export default PostUploadModal;
그리고 이 모달을 사용할 수 있습니다.
import { useState } from 'react';
import PostUploadModal, {
PostUploadModalProps,
} from './components/modals/PostUploadModal';
function App() {
const [postUploadModalProps, setPostuploadModalsProps] = useState<
PostUploadModalProps | undefined
>();
const openPostUploadModal = () => {
setPostuploadModalsProps({
onClose: () => setPostuploadModalsProps(undefined),
visible: true,
onSubmit: (title, content) => console.log(title, content),
});
};
return (
<div>
{postUploadModalProps && <PostUploadModal {...postUploadModalProps} />}
<button onClick={openPostUploadModal}>Open PostUploadModal</button>
</div>
);
}
export default App;
이런 식으로 호출하면 모달이 필요한 모든 위치에 모달의 상태를 정의해야 합니다.
내 프로젝트에서 사용하는 전략을 공유하겠습니다. 시작하기 전에 프로젝트에 따라 약간 다르게 구현된다는 점을 알고 있어야 합니다.
5가지 모드를 보여드리겠습니다.
모달 컨텍스트
모달을 만들기 전에
React.Context
를 사용하여 기본을 구현해야 합니다.[후크/useModal.tsx]
import React, { createContext, useCallback, useContext, useState } from 'react';
interface IModalContext {}
const ModalContext = createContext<IModalContext>({} as IModalContext);
const useDefaultModalLogic = <T extends unknown>() => {
const [visible, setVisible] = useState(false);
const [props, setProps] = useState<T | undefined>();
const openModal = useCallback((props?: T) => {
setProps(props);
setVisible(true);
}, []);
const closeModal = useCallback(() => {
setProps(undefined);
setVisible(false);
}, []);
return {
visible,
props,
openModal,
closeModal,
};
};
export const useModal = () => useContext(ModalContext);
export const ModalContextProvider = ({
children,
}: {
children?: React.ReactNode;
}) => {
const modalContextValue: IModalContext = {};
return (
<ModalContext.Provider value={modalContextValue}>
{children}
</ModalContext.Provider>
);
};
[유형/modal.ts]
export interface IModal {
onClose?: VoidFunction;
visible?: boolean;
}
export type OpenModal<T> = (params: T) => void;
[앱.tsx]
import Examples from './components/Examples';
import { ModalContextProvider } from './hooks/useModal';
function App() {
return (
<ModalContextProvider>
<Examples />
</ModalContextProvider>
);
}
export default App;
App.tsx
에 있습니다. 알리다
[components/modals/Alert/index.tsx]
import Box from '@mui/material/Box';
import Modal from '@mui/material/Modal';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import { IModal } from '../../../types/modal';
export interface AlertProps extends IModal {
title?: string;
message?: string;
onOk?: VoidFunction;
}
const style = {
position: 'absolute' as 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '1px solid #000',
boxShadow: 24,
p: 4,
};
const Alert = ({
visible = false,
onClose,
title,
message,
onOk,
}: AlertProps) => {
const handleOk = () => {
onOk?.();
onClose?.();
};
return (
<Modal open={visible} onClose={onClose}>
<Box sx={style}>
{title && (
<Typography variant="h6" component="h2">
{title}
</Typography>
)}
{message && <Typography sx={{ mt: 2 }}>{message}</Typography>}
<Grid container justifyContent="flex-end">
<Button onClick={handleOk}>OK</Button>
</Grid>
</Box>
</Modal>
);
};
export default Alert;
[후크/useModal.tsx]
...
interface IModalContext {
openAlert: OpenModal<AlertProps>;
}
...
export const ModalContextProvider = ({
children,
}: {
children?: React.ReactNode;
}) => {
const {
openModal: openAlert,
closeModal: closeAlert,
props: alertProps,
visible: alertVisible,
} = useDefaultModalLogic<AlertProps>();
const modalContextValue: IModalContext = {
openAlert,
};
return (
<ModalContext.Provider value={modalContextValue}>
{alertProps && (
<Alert {...alertProps} onClose={closeAlert} visible={alertVisible} />
)}
{children}
</ModalContext.Provider>
);
};
[구성품/예제]
import Container from '@mui/material/Container';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import { useModal } from '../../hooks/useModal';
function Examples() {
const { openAlert } = useModal();
const openAlertExample = () => {
openAlert({
title: 'Alert Example',
message: 'Hello Dev.to!',
});
};
return (
<Container maxWidth="sm" sx={{ textAlign: 'center', marginTop: 12 }}>
<Grid container spacing={2} direction="column">
<Grid item>
<Button variant="contained" onClick={openAlertExample}>
Alert
</Button>
</Grid>
</Grid>
</Container>
);
}
export default Examples;
함수를 호출하는 것처럼 모달을 엽니다
Alert
.openAlert({
title: 'Alert Example',
message: 'Hello Dev.to!',
});
필요한 경우
onOk
콜백 함수를 전달할 수 있습니다.확인하다
[components/modals/Confirm/index.tsx]
import Box from '@mui/material/Box';
import Modal from '@mui/material/Modal';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import { IModal } from '../../../types/modal';
export interface ConfirmProps extends IModal {
title?: string;
message?: string;
cancelText?: string;
confirmText?: string;
onCancel?: VoidFunction;
onConfirm?: VoidFunction;
}
const style = {
position: 'absolute' as 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '1px solid #000',
boxShadow: 24,
p: 4,
};
const Confirm = ({
visible = false,
onClose,
title,
message,
cancelText,
onCancel,
confirmText,
onConfirm,
}: ConfirmProps) => {
const handleCancel = () => {
onCancel?.();
onClose?.();
};
const handleConfirm = () => {
onConfirm?.();
onClose?.();
};
return (
<Modal open={visible} onClose={onClose}>
<Box sx={style}>
{title && (
<Typography variant="h6" component="h2">
{title}
</Typography>
)}
{message && <Typography sx={{ mt: 2 }}>{message}</Typography>}
<Grid container justifyContent="flex-end">
<Button onClick={handleCancel}>{cancelText}</Button>
<Button onClick={handleConfirm}>{confirmText}</Button>
</Grid>
</Box>
</Modal>
);
};
export default Confirm;
[후크/useModal.tsx]
interface IModalContext {
openAlert: OpenModal<AlertProps>;
openConfirm: OpenModal<ConfirmProps>;
}
...
const {
openModal: openConfirm,
closeModal: closeConfirm,
props: confirmProps,
visible: confirmVisible,
} = useDefaultModalLogic<ConfirmProps>();
...
const modalContextValue: IModalContext = {
openAlert,
openConfirm,
};
return (
<ModalContext.Provider value={modalContextValue}>
{alertProps && (
<Alert {...alertProps} onClose={closeAlert} visible={alertVisible} />
)}
{confirmProps && (
<Confirm
{...confirmProps}
onClose={closeConfirm}
visible={confirmVisible}
/>
)}
{children}
</ModalContext.Provider>
);
...
[components/Examples/index.tsx]
const openConfirmExample = () => {
openConfirm({
title: 'Confirm Example',
message: 'Do you like this post?',
cancelText: 'NO',
confirmText: 'YES',
onCancel: () => openAlert({ message: 'clicked NO' }),
onConfirm: () => openAlert({ message: 'clicked YES' }),
});
};
Alert
와 유사하며 더 많은 소품을 가져오고 모달에서 다른 모달을 열 수 있음을 보여줍니다.소품이 없는 모달
[컴포넌트/모달/GuideModal/index.tsx]
import Box from '@mui/material/Box';
import Modal from '@mui/material/Modal';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import { IModal } from '../../../types/modal';
const style = {
position: 'absolute' as 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '1px solid #000',
boxShadow: 24,
p: 4,
};
const GuideModal = ({ visible = false, onClose }: IModal) => {
return (
<Modal open={visible} onClose={onClose}>
<Box sx={style}>
<Typography variant="h6" component="h2">
Guide
</Typography>
<Typography sx={{ mt: 2 }}>Some Text...</Typography>
<Grid container justifyContent="flex-end">
<Button onClick={onClose}>OK</Button>
</Grid>
</Box>
</Modal>
);
};
export default GuideModal;
[후크/useModal.tsx]
...
interface IModalContext {
openAlert: OpenModal<AlertProps>;
openConfirm: OpenModal<ConfirmProps>;
openGuideModal: VoidFunction;
}
...
const {
openModal: openGuideModal,
closeModal: closeGuideModal,
visible: guideModalVisible,
} = useDefaultModalLogic<unknown>();
...
<GuideModal onClose={closeGuideModal} visible={guideModalVisible} />
...
[components/Examples/index.tsx]
...
const openGuideModalExample = () => {
openGuideModal();
};
...
GuideModal
소품이 없습니다. 따라서 매개변수의 유형은 VoidFunction
이고 unknown
를 제네릭 유형인 useDefaultModalLogic
로 전달합니다.입력 양식
[컴포넌트/모달/PostUploadModal/index.tsx]
import { useForm } from 'react-hook-form';
import Box from '@mui/material/Box';
import Modal from '@mui/material/Modal';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import { IModal } from '../../../types/modal';
export interface PostUploadModalProps extends IModal {
onSubmit?: (title: string, content: string) => void;
}
const style = {
position: 'absolute' as 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '1px solid #000',
boxShadow: 24,
p: 4,
};
const PostUploadModal = ({
visible = false,
onClose,
onSubmit,
}: PostUploadModalProps) => {
const { register, handleSubmit: handleFormSubmit } = useForm<{
title: string;
content: string;
}>();
const handleSubmit: Parameters<typeof handleFormSubmit>[0] = (values) => {
onSubmit?.(values.title, values.content);
onClose?.();
};
return (
<Modal open={visible} onClose={onClose}>
<Box sx={style}>
<TextField
{...register('title', { required: true })}
sx={{ width: '100%', marginBottom: 2 }}
label="Title"
placeholder="Enter the title"
/>
<TextField
{...register('content', { required: true })}
sx={{ width: '100%', marginBottom: 2 }}
label="Content"
multiline
rows={4}
placeholder="Enter the content"
/>
<Grid container justifyContent="flex-end">
<Button
variant="contained"
color="success"
onClick={handleFormSubmit(handleSubmit)}
>
Submit
</Button>
</Grid>
</Box>
</Modal>
);
};
export default PostUploadModal;
[후크/useModal.tsx]
...
interface IModalContext {
openAlert: OpenModal<AlertProps>;
openConfirm: OpenModal<ConfirmProps>;
openGuideModal: VoidFunction;
openPostUploadModal: OpenModal<PostUploadModalProps>;
}
...
const {
openModal: openPostUploadModal,
closeModal: closePostUploadModal,
visible: postUploadModalVisible,
props: postUploadModalProps,
} = useDefaultModalLogic<PostUploadModalProps>();
...
{postUploadModalProps && (
<PostUploadModal
{...postUploadModalProps}
onClose={closePostUploadModal}
visible={postUploadModalVisible}
/>
)}
...
[components/Examples/index.tsx]
...
const openPostUploadModalExample = () => {
openPostUploadModal({
onSubmit: (title, content) => {
openAlert({
title: 'Form Data',
message: `title: ${title} content: ${content}`,
});
},
});
};
...
PostUploadModal
는 내부에 react-hook-form
를 사용하고 값이 확인되면 입력 필드 값을 onSubmit
에 전달합니다.
Parameters<typeof handleFormSubmit>[0]
: Gets first parameter type of handleFormSubmit
API를 호출하는 모달
[컴포넌트/모달/APICallModal/index.tsx]
import Box from '@mui/material/Box';
import Modal from '@mui/material/Modal';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import { IModal } from '../../../types/modal';
import { useEffect, useState } from 'react';
import { useModal } from '../../../hooks/useModal';
export interface APICallModalProps extends IModal {
postId: number;
}
const style = {
position: 'absolute' as 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '1px solid #000',
boxShadow: 24,
p: 4,
};
const APICallModal = ({
visible = false,
onClose,
postId,
}: APICallModalProps) => {
const [loading, setLoading] = useState(true);
const [title, setTitle] = useState<string>('');
const { openAlert } = useModal();
useEffect(() => {
const fetchPost = async (postId: number) => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts/${postId}`
);
try {
if (res.status !== 200) {
throw new Error(`status is ${res.status}`);
}
const json = await res.json();
setTitle(json.title);
setLoading(false);
} catch {
openAlert({ message: 'API Error' });
}
};
fetchPost(postId);
}, [postId]);
return (
<Modal open={visible} onClose={onClose}>
<Box sx={style}>
<Typography variant="h6" component="h2">
{loading ? 'loading...' : title}
</Typography>
<Grid container justifyContent="flex-end">
<Button onClick={onClose}>Close</Button>
</Grid>
</Box>
</Modal>
);
};
export default APICallModal;
[후크/useModal.tsx]
...
interface IModalContext {
openAlert: OpenModal<AlertProps>;
openConfirm: OpenModal<ConfirmProps>;
openGuideModal: VoidFunction;
openPostUploadModal: OpenModal<PostUploadModalProps>;
openAPICallModal: OpenModal<APICallModalProps>;
}
...
const {
openModal: openAPICallModal,
closeModal: closeAPICallModal,
visible: openAPICallModalVisible,
props: openAPICallModalProps,
} = useDefaultModalLogic<APICallModalProps>();
...
{openAPICallModalProps && (
<APICallModal
{...openAPICallModalProps}
onClose={closeAPICallModal}
visible={openAPICallModalVisible}
/>
)}
...
[components/Examples/index.tsx]
...
const openAPICallModalExample = () => {
openAPICallModal({
postId: 1,
});
};
...
API에
JSONPlaceholder
를 사용했습니다. 모달은 ID를 가져오고 useEffect
에서 API를 요청합니다.결론
Preview
Github Source Code
이것이 내가
React
에서 모달 구성 요소를 관리하는 방법입니다. 이것이 좋은 방법인지는 모르겠지만 만족합니다. 그것은 내 시간을 절약합니다. 나는 모달 코드를 작성하고 모달을 공급자에 넣고 필요할 때 open 함수를 호출합니다. 그것은 일종의 규칙입니다. 나는 그것을 따릅니다. '규칙이 있다'는 게 장점이라고 생각해요.프로젝트에서 모달 구성 요소를 어떻게 관리합니까?
Reference
이 문제에 관하여(React TS: 모달 구성 요소를 관리하는 방법(Custom Modal Hook)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/lico/react-ts-how-i-manage-modal-components-custom-modals-hook-4nmg텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)