Zeigen eines modalen Dialogs mit useImperativeHandle() React Hook

Nehmen Sie Material-UIs Dialog Komponente als ein Beispiel, das hat open: boolean Reagieren Sie als Requisite, um den offenen/geschlossenen Zustand zu verwalten. In der Material-UI Dokumentation finden Sie ein ähnliches Anwendungsbeispiel:

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>
  );
}

Im ursprünglichen Beispiel wird der Dialog an Ort und Stelle verwendet. Normalerweise möchten Sie Dialoge in einer eigenständigen Komponente extrahieren, zum Beispiel:

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">;

Danach könnte das ursprüngliche Beispiel wie folgt reduziert werden:

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>
  );
}

Wenn der Dialog verwendet werden kann, ohne dass sein Zustand vor Ort verwaltet werden muss, würde dieser Code schön und sauber aussehen.

Es gibt mehrere Möglichkeiten, dies zu implementieren, z. B. durch die Einführung einer obersten Ebene DialogProvider Komponente + useDialog(...) Hook reagieren, alternativ können Sie dem Dialog selbst einen imperativen Handler hinzufügen, damit er mit geöffnet werden kann dialogRef.current?.open() -Methode, die in der Dialoginstanz verfügbar ist.

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>
  );
}

Sehen wir uns nun an, wie die Implementierung dieses Dialogs einschließlich .open() Methode implementiert mit useImeprativeHandle(ref, ...) Reaktionshaken sehen so aus:

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;
};

Es gibt Vor- und Nachteile dieses Ansatzes, auf der guten Seite ist, dass er vollständig in sich geschlossen ist und nicht auf externe Zustandsverwaltungslösungen angewiesen ist.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *