
在 React Native 开发中,我们经常需要弹出对话框、提示框、日期选择器或者其他 overlay 组件。传统方式通常依赖 state + conditional rendering + 回调函数,但在复杂场景下,代码会变得分散、难维护,尤其是当多个弹窗同时存在时。
react-native-promise-portal 提供了一种 Promise + Portal 的解决方案,让你可以像调用普通异步函数一样调用弹窗,同时保持逻辑线性、易于管理。
核心特点
-
Promise-first 弹窗调用
弹窗调用返回 Promise,你可以用
await直接获取用户操作结果。业务逻辑与 UI 逻辑解耦,写出来的代码更直观。 -
支持多重弹窗 & name 去重
- 可以在同一页面同时显示多个弹窗,每个弹窗独立关闭。
- 通过
index控制渲染层级,index 越大,显示在最上层。 - 通过
name去重,避免重复弹窗触发业务冲突。
-
局部 Portal
- 支持在特定页面或模块内管理弹窗,不影响全局 Portal 状态。
- 跨组件调用弹窗,保证弹窗只在目标页面显示。
- 避免不同页面弹窗相互覆盖或冲突,提升模块化管理能力。
-
脱离 Hook 调用局部 UI
- 使用
PortalManager可以在 非组件上下文 触发局部弹窗。 - 适用于后台逻辑、网络请求回调、定时任务等场景。
- Promise 接口仍然保持逻辑线性。
- 使用
-
基于 Portal 渲染
- 利用 React Native 的 Portal 技术,将弹窗渲染到顶层,解决 z-index 和 clipping 问题。
基本使用
1. 设置 PortalProvider
tsx
import { PortalProvider } from 'react-native-promise-portal';
export default function RootLayout() {
return <PortalProvider>{/* Your app content */}</PortalProvider>;
}
2. 使用 Hook 调用弹窗
tsx
import { usePortal } from 'react-native-promise-portal';
function MyComponent() {
const { showWithOverlay } = usePortal();
const handleShowDialog = async () => {
try {
const result = await showWithOverlay<boolean>({
component: ({ close }) => (
<Confirm title="Confirm" subTitle="Are you sure?" close={close} />
),
});
console.log('Result:', result);
} catch (error) {
console.error(error);
}
};
return <Button onPress={handleShowDialog} title="Show Dialog" />;
}
多重弹窗与 name 去重示例
tsx
const { showWithOverlay } = usePortal();
// 弹出第一个弹窗
await showWithOverlay({
name: 'confirm-delete',
component: ({ close }) => <Confirm close={close} />,
});
// 再次调用同名弹窗会抛出 PortalAlreadyExistsError
await showWithOverlay({
name: 'confirm-delete',
component: ({ close }) => <Confirm close={close} />,
});
通过 index 可以控制层级顺序:
tsx
const promise1 = showWithOverlay({ title: 'Dialog 1', index: 1 });
const promise2 = showWithOverlay({ title: 'Dialog 2', index: 10 }); // 更高层级
const promise3 = showWithOverlay({ title: 'Dialog 3', index: 5 });
Promise.allSettled([promise1, promise2, promise3]).then((results) => {
console.log('All dialogs closed:', results);
});
局部 Portal 解决的业务痛点
在大型应用中,某些弹窗只在特定页面显示,使用全局 Portal 管理容易导致以下问题:
- 弹窗状态全局混乱
- 不同页面弹窗可能相互覆盖
- 逻辑与 UI 耦合,调用分散
局部 Portal 通过 PortalManager 提供页面级管理:
tsx
import { PortalRender, PortalManager } from 'react-native-promise-portal';
import { useLocalPortals } from './helper/LocalPortal';
function HomePage() {
const homePortalContent = useLocalPortals((state) => state.homePortalContent);
const handleShowLocalPortal = async () => {
await HomePagePortalManager.showWithOverlay<boolean>({
component: ({ close }) => (
<Confirm
title="Local portal"
subTitle="Only on home page"
close={close}
/>
),
});
};
return (
<>
<Button onPress={handleShowLocalPortal} title="Show Local Portal" />
<PortalRender portals={homePortalContent} />
</>
);
}
局部 Portal 优势:
- 弹窗只影响特定页面或模块
- 跨组件 / 跨 Hook 调用更灵活
- 避免全局冲突,提高模块化和可维护性
脱离 Hook 调用局部 UI
有些业务场景中,你可能希望在 非 React 组件上下文 或 Hook 不方便使用的地方 调用弹窗,例如网络请求回调或定时任务。
tsx
import { HomePagePortalManager } from './helper/LocalPortal';
async function handleAsyncEvent() {
try {
const result = await HomePagePortalManager.showWithOverlay<boolean>({
component: ({ close }) => (
<Confirm
title="Async Event"
subTitle="This dialog is triggered outside React component"
close={close}
/>
),
overlay: { orientation: 'centerMiddle' },
});
console.log('User result:', result);
} catch (err) {
console.error('Portal closed or error:', err);
}
}
特点:
- 脱离组件 / Hook,任意业务逻辑可触发
- 弹窗仅显示在目标页面或模块
- Promise 接口保持逻辑线性
更多示例
Loading 指示器
tsx
const { showWithOverlay } = usePortal();
const showLoading = () => {
const { close } = showWithOverlay<void>({
component: () => <ActivityIndicator />,
overlay: { closeable: false },
});
setTimeout(() => close(), 3000);
};
日期选择器(底部弹出)
tsx
const date = await showWithOverlay<string>({
component: ({ close }) => (
<DatePicker
onSelect={(date) => close(date)}
onCancel={() => close(new Error('Cancelled'))}
/>
),
overlay: { orientation: 'centerBottom' },
});
错误处理
tsx
import { PortalError } from 'react-native-promise-portal';
try {
const result = await showWithOverlay<boolean>({
component: ({ close }) => <Confirm close={close} />,
});
} catch (error) {
if (error instanceof PortalError) {
if (error.isCloseByOverlayPressError()) console.log('Closed by overlay');
else if (error.isCloseByHardwareBackPressError()) console.log('Closed by back button');
else if (error.isPortalAlreadyExistsError()) console.log('Portal already exists');
}
}
总结
react-native-promise-portal 将弹窗调用封装为异步函数,支持:
- 多重弹窗
- name 去重机制
- 局部 Portal
- 脱离 Hook 调用局部 UI
- Promise 异步接口
它极大简化了 React Native 弹窗管理,保证 UI 与业务逻辑解耦,调用方式直观、可维护,是复杂页面交互场景下的理想选择。
安装
arduino
npm install react-native-promise-portal
如果你喜欢这个库,欢迎点个 ⭐️ 支持一下!
