更多例子
自定义弹窗
openify 支持任意类型可打开的组件,只要它能实现OpenParams
中的open
,onClose
,onUnmount
即可
查看代码
tsx
import { Button, Form, Input, Modal, message } from "antd";
import { type OpenParams, Slot, openify } from "openify";
import React, { useEffect, useState } from "react";
import { slotId, wait } from "./utils";
type CustomModalProps = {
open?: boolean;
id: string;
onOk?: (id: string) => void;
onCancel?: () => void;
afterClose?: () => void;
};
const CustomModal = ({
open,
id,
onOk,
onCancel,
afterClose,
}: CustomModalProps) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
// biome-ignore lint/correctness/useExhaustiveDependencies(form.setFieldValue): <explanation>
// biome-ignore lint/correctness/useExhaustiveDependencies(id): <explanation>
useEffect(() => {
if (open) {
form.setFieldValue("id", id);
}
}, [open]);
return (
<Modal
title="自定义弹窗"
open={open}
confirmLoading={loading}
onOk={async () => {
const nextId = form.getFieldValue("id");
setLoading(true);
await wait(1000);
setLoading(false);
onOk?.(nextId);
}}
onCancel={onCancel}
afterClose={afterClose}
>
<Form form={form}>
<Form.Item label="ID" name="id">
<Input />
</Form.Item>
</Form>
</Modal>
);
};
type OpenableCustomModalParams = OpenParams<string | null> &
Omit<CustomModalProps, "open" | "onOk" | "onCancel" | "afterClose">;
const openableCustomModal = openify<OpenableCustomModalParams>(
({ open, onClose, onUnmount, ...rest }) => (
<CustomModal
{...rest}
open={open}
onOk={(taskId) => onClose(taskId)}
onCancel={() => onClose(null)}
afterClose={onUnmount}
/>
),
);
export default () => {
const [form] = Form.useForm();
return (
<>
<Form form={form}>
<Form.Item label="ID" name="id">
<Input />
</Form.Item>
</Form>
<Button
onClick={async () => {
const id = form.getFieldValue("id");
const nextId = await Slot.getById(slotId).open(
openableCustomModal,
{ id },
);
if (nextId !== null) {
message.success(`生成新ID: ${nextId}`);
form.setFieldValue("id", nextId);
} else {
message.warning("弹窗取消");
}
}}
>
自定义弹窗
</Button>
</>
);
};
错误捕获
弹窗异常崩溃时,不会影响主应用,并且会reject
对应错误至open
生成的Promise
查看代码
tsx
import { Button, Modal, message } from "antd";
import { type OpenParams, Slot, openify } from "openify";
import React, { useState } from "react";
import { slotId } from "./utils";
type CatchErrorProps = {
open?: boolean;
onClose?: () => void;
afterClose?: () => void;
};
const CatchError = ({ open, onClose, afterClose }: CatchErrorProps) => {
const [hasError, setHasError] = useState(false);
if (hasError) {
throw new Error("模拟错误");
}
return (
<Modal
title="错误捕获"
open={open}
onOk={onClose}
onCancel={onClose}
afterClose={afterClose}
>
<Button onClick={() => setHasError(true)}>触发异常</Button>
</Modal>
);
};
type OpenableCatchErrorParams = OpenParams<string | null> &
Omit<CatchErrorProps, "open" | "onOk" | "onCancel" | "afterClose">;
const openableCatchError = openify<OpenableCatchErrorParams>(
({ open, onClose, onUnmount, ...rest }) => (
<CatchError
{...rest}
open={open}
onClose={onClose}
afterClose={onUnmount}
/>
),
);
export default () => {
return (
<Button
onClick={async () => {
try {
await Slot.getById(slotId).open(openableCatchError);
message.success("弹窗正确关闭");
} catch (e) {
console.error(e);
message.error("弹窗异常退出");
}
}}
>
异常弹窗
</Button>
);
};
获取Context
将Slot
放在合适的位置,就能拿到对应位置的Context
查看代码
tsx
import { Button, Modal, Space } from "antd";
import { type OpenParams, Slot, openify } from "openify";
import React, { createContext, useContext } from "react";
import { slotId } from "./utils";
const MyContext = createContext("默认Context");
type GetContextProps = {
open?: boolean;
onClose?: () => void;
afterClose?: () => void;
};
const GetContext = ({ open, onClose, afterClose }: GetContextProps) => {
const contextValue = useContext(MyContext);
return (
<Modal
title="获取Context"
open={open}
onOk={onClose}
onCancel={onClose}
afterClose={afterClose}
>
{`当前Context值: ${contextValue}`}
</Modal>
);
};
type OpenableGetContextParams = OpenParams<string | null> &
Omit<GetContextProps, "open" | "onOk" | "onCancel" | "afterClose">;
const openableGetContext = openify<OpenableGetContextParams>(
({ open, onClose, onUnmount, ...rest }) => (
<GetContext
{...rest}
open={open}
onClose={onClose}
afterClose={onUnmount}
/>
),
);
export default () => {
return (
<MyContext.Provider value="自定义Context">
<Space size={16}>
<Button
onClick={async () => {
await Slot.getById(slotId).open(openableGetContext);
}}
>
默认Context
</Button>
<Button
onClick={async () => {
await Slot.getById("in-context").open(
openableGetContext,
);
}}
>
自定义Context
</Button>
</Space>
<Slot id="in-context" />
</MyContext.Provider>
);
};
外部关闭
open
返回的是一个带cancel
的Promise
,可以手动调用cancel
关闭弹窗,传入参数同onClose
查看代码
tsx
import { Button, Modal, message } from "antd";
import { type OpenParams, Slot, openify } from "openify";
import React from "react";
import { slotId } from "./utils";
type CloseOutsideProps = {
open?: boolean;
onClose?: (reaseon: string) => void;
afterClose?: () => void;
};
const CloseOutside = ({ open, onClose, afterClose }: CloseOutsideProps) => {
return (
<Modal
title="外部关闭"
open={open}
onOk={() => onClose?.("内部确定")}
onCancel={() => onClose?.("内部取消")}
afterClose={afterClose}
/>
);
};
type OpenableCloseOutsideParams = OpenParams<string> &
Omit<CloseOutsideProps, "open" | "onClose" | "afterClose">;
const openableCloseOutside = openify<OpenableCloseOutsideParams>(
({ open, onClose, onUnmount, ...rest }) => (
<CloseOutside
{...rest}
open={open}
onClose={onClose}
afterClose={onUnmount}
/>
),
);
export default () => {
return (
<Button
onClick={async () => {
const res = Slot.getById(slotId).open(openableCloseOutside);
setTimeout(() => {
res.cancel("外部关闭");
}, 5000);
const reason = await res;
message.info(`关闭原因: ${reason}`);
}}
>
打开弹窗
</Button>
);
};
渲染隔离
Slot
外部组件和内部组件之间的渲染是独立的,自身更新不会带来另一方的非必要渲染
注意
Context
或Store
之类的更新依然会生效
查看代码
tsx
import { Button, Modal, Space, message } from "antd";
import { type OpenParams, Slot, openify } from "openify";
import React, { useEffect, useRef, useState } from "react";
import { slotId } from "./utils";
type RenderSelfProps = {
open?: boolean;
onClose?: () => void;
afterClose?: () => void;
};
const RenderSelf = ({ open, onClose, afterClose }: RenderSelfProps) => {
const [flag, update] = useState({});
const initRef = useRef(false);
// biome-ignore lint/correctness/useExhaustiveDependencies(flag): <explanation>
useEffect(() => {
if (initRef.current) {
message.info("弹窗渲染");
} else {
initRef.current = true;
}
}, [flag]);
const [timeId, setTimeId] = useState<number | null>(null);
useEffect(() => {
return () => {
if (timeId) {
clearTimeout(timeId);
}
};
}, [timeId]);
return (
<Modal
title="渲染隔离"
open={open}
onOk={onClose}
onCancel={onClose}
afterClose={afterClose}
>
<Button
onClick={() => {
if (timeId === null) {
setTimeId(
window.setInterval(() => {
update({});
}, 1000),
);
} else {
setTimeId(null);
}
}}
>
{timeId === null ? "开始刷新" : "停止刷新"}
</Button>
</Modal>
);
};
type OpenableRenderSelfParams = OpenParams<void> &
Omit<RenderSelfProps, "open" | "onClose" | "afterClose">;
const openableRenderSelf = openify<OpenableRenderSelfParams>(
({ open, onClose, onUnmount, ...rest }) => (
<RenderSelf
{...rest}
open={open}
onClose={onClose}
afterClose={onUnmount}
/>
),
);
export default () => {
const [flag, update] = useState({});
const initRef = useRef(false);
// biome-ignore lint/correctness/useExhaustiveDependencies(flag): <explanation>
useEffect(() => {
if (initRef.current) {
message.info("App渲染");
} else {
initRef.current = true;
}
}, [flag]);
const [timeId, setTimeId] = useState<number | null>(null);
useEffect(() => {
return () => {
if (timeId) {
clearTimeout(timeId);
}
};
}, [timeId]);
return (
<Space size={12}>
<Button
onClick={() => {
if (timeId === null) {
setTimeId(
window.setInterval(() => {
update({});
}, 1000),
);
} else {
setTimeId(null);
}
}}
>
{timeId === null ? "开始刷新" : "停止刷新"}
</Button>
<Button
onClick={async () => {
await Slot.getById(slotId).open(openableRenderSelf);
}}
>
打开弹窗
</Button>
</Space>
);
};