Отображение модального диалога с помощью useImperativeHandle() React hook
Возьмите пользовательский интерфейс материалов Dialog
компонент в качестве примера, который имеет open: boolean
React prop как способ управления его открытым/закрытым состоянием. В пользовательском интерфейсе материала документация вы найдете пример использования, подобный этому:
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 hook, в качестве альтернативы вы можете добавить императивный обработчик в само диалоговое окно, чтобы его можно было открыть с помощью 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, ...)
Реагирующие хуки выглядят так:
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;
};
У этого подхода есть свои плюсы и минусы, плюс в том, что он полностью автономен и не зависит от каких-либо внешних решений по управлению состоянием.