useImperativeHandle() React 후크가 있는 모달 대화 상자 표시

Material UI의 Dialog 구성 요소를 예로 들어 open: boolean React prop을 열림/닫힘 상태를 관리하는 방법으로 사용합니다. Material UIdocumentation에서 다음과 유사한 사용 예를 찾을 수 있습니다.

import * as React from "react";
import { Button, Container, Dialog, DialogActions, DialogContent, DialogTitle } from "@mui/material";

export function Example(): JSX.Element {
  const [open, setOpen] = React.useState(false);
  const handleOpen = React.useCallback(() => setOpen(true), []);
  const handleClose = React.useCallback(() => setOpen(false), []);
  const handleAction = React.useCallback(() => { ... }, []);

  return (
    <Container>
       <Button onClick={handleOpen}>Open Dialog</Button>

       <Dialog open={state.open} onClose={handleClose}>
         <DialogTitle>...</DialogTitle>
        <DialogContent>
          ...
        </DialogContent>
        <DialogActions>
          <Button onClick={handleClose}>Cancel</Button>
          <Button onClick={handleAction}>OK</Button>
        </DialogActions>
      </Dialog>
    </Container>
  );
}


원래 예에서는 대화 상자가 제자리에서 사용됩니다. 일반적으로 독립 실행형 구성 요소에서 대화 상자를 추출하려고 합니다. 예를 들면 다음과 같습니다.

import * as React from "react";
import { Button, Container, Dialog, DialogActions, DialogContent, DialogProps, DialogTitle } from "@mui/material";

export function ConfirmDialog(props: ConfirmDialogProps): JSX.Element {
  const [state, setState] = ...
  const handleClose = ...
  const handleConfirm = ...

  return (
    <Dialog open={state.open} {...props}>
      <DialogTitle>...</DialogTitle>
      <DialogContent>
        ...
      </DialogContent>
      <DialogActions>
          <Button onClick={handleClose}>Cancel</Button>
          <Button onClick={handleConfirm}>OK</Button>
      </DialogActions>
    </Dialog>
  );
}

export type ConfirmDialogProps = Omit<DialogProps, "open">;


그런 다음 원래 예를 다음과 같이 줄일 수 있습니다.

import * as React from "react";
import { ConfirmDialog } from "../dialogs/ConfirmDialog.js";

export function Example(): JSX.Element {
  const handleOpen = ...
  const handleAction = ...

  return (
    <Container>
       <Button onClick={handleOpen}>Open Dialog</Button>
       <ConfirmDialog onConfirm={handleAction} />
    </Container>
  );
}


상태를 내부에서 관리할 필요 없이 대화 상자를 사용할 수 있다면 코드가 멋지고 깨끗해 보일 것입니다.

이를 구현하는 방법에는 여러 가지가 있습니다. 최상위 DialogProvider 구성 요소 + useDialog(...) React 후크를 도입하여 대화 인스턴스에서 사용할 수 있는 dialogRef.current?.open() 메서드를 사용하여 열 수 있도록 대화 자체에 명령형 핸들러를 추가할 수 있습니다.

import * as React from "react";
import { ConfirmDialog } from "../dialogs/ConfirmDialog.js";

export function Example(): JSX.Element {
  const dialogRef = React.useRef<DialogElement>(null);
  const handleOpen = React.useCallback(() = dialogRef.current?.open(), []);
  const handleAction = ...

  return (
    <Container>
       <Button onClick={handleOpen}>Open Dialog</Button>
       <ConfirmDialog ref={dialogRef} onConfirm={handleAction} />
    </Container>
  );
}


이제 .open() , useImeprativeHandle(ref, ...) React 후크로 구현된 useState() 메서드를 포함하여 이 대화 상자의 구현이 어떻게 보이는지 살펴보겠습니다.

import * as React from "react";
import { Button, Dialog, DialogActions, DialogContent, DialogProps, DialogTitle } from "@mui/material";

export const ConfirmDialog = React.forwardRef<
  DialogElement,
  ConfirmDialogProps
>(function ConfirmDialog(props, ref): JSX.Element {
  const { onClose, onConfirm, ...other } = props;
  const [state, setState] = React.useState<State>({ open: false });
  const handleClose = useHandleClose(setState, onClose);
  const handleConfirm = useHandleConfirm(setState, onConfirm);

  React.useImperativeHandle(ref, () => ({
    open() {
      setState({ open: true });
    },
  }));

  return (
    <Dialog open={state.open} onClose={handleClose} {...other}>
      <DialogTitle>...</DialogTitle>
      <DialogContent>...</DialogContent>
      <DialogActions>
        <Button onClick={handleClose}>Cancel</Button>
        <Button onClick={handleConfirm}>OK</Button>
      </DialogActions>
    </Dialog>
  );
});

function useHandleClose(setState: SetState, handleClose?: CloseHandler) {
  return React.useCallback<CloseHandler>(function (event, reason) {
    setState({ open: false });
    handleClose?.(event, reason ?? "backdropClick");
  }, []);
}

function useHandleConfirm(setState: SetState, handleConfirm?: ConfirmHandler) {
  return React.useCallback(async function () {
    await handleConfirm?.();
    setState({ open: false });
  }, []);
}

type State = { open: boolean; error?: Error };
type SetState = React.Dispatch<React.SetStateAction<State>>;
type CloseHandler = NonNullable<DialogProps["onClose"]>;
type ConfirmHandler = () => Promise<void> | void;

export type DialogElement = { open: () => void };

export type ConfirmDialogProps = Omit<DialogProps, "open"> & {
  onConfirm?: ConfirmHandler;
};


이 접근 방식에는 장단점이 있지만 좋은 점은 완전히 독립적이며 외부 상태 관리 솔루션에 의존하지 않는다는 것입니다.

https://github.com/kriasoft/react-starter-kit/discussions/2004

좋은 웹페이지 즐겨찾기