react-native-promise-portal:React Native 弹窗管理的新思路


在 React Native 开发中,我们经常需要弹出对话框、提示框、日期选择器或者其他 overlay 组件。传统方式通常依赖 state + conditional rendering + 回调函数,但在复杂场景下,代码会变得分散、难维护,尤其是当多个弹窗同时存在时。

react-native-promise-portal 提供了一种 Promise + Portal 的解决方案,让你可以像调用普通异步函数一样调用弹窗,同时保持逻辑线性、易于管理。

核心特点

  1. Promise-first 弹窗调用

    弹窗调用返回 Promise,你可以用 await 直接获取用户操作结果。业务逻辑与 UI 逻辑解耦,写出来的代码更直观。

  2. 支持多重弹窗 & name 去重

    • 可以在同一页面同时显示多个弹窗,每个弹窗独立关闭。
    • 通过 index 控制渲染层级,index 越大,显示在最上层。
    • 通过 name 去重,避免重复弹窗触发业务冲突。
  3. 局部 Portal

    • 支持在特定页面或模块内管理弹窗,不影响全局 Portal 状态。
    • 跨组件调用弹窗,保证弹窗只在目标页面显示。
    • 避免不同页面弹窗相互覆盖或冲突,提升模块化管理能力。
  4. 脱离 Hook 调用局部 UI

    • 使用 PortalManager 可以在 非组件上下文 触发局部弹窗。
    • 适用于后台逻辑、网络请求回调、定时任务等场景。
    • Promise 接口仍然保持逻辑线性。
  5. 基于 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 优势:

  1. 弹窗只影响特定页面或模块
  2. 跨组件 / 跨 Hook 调用更灵活
  3. 避免全局冲突,提高模块化和可维护性

脱离 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

如果你喜欢这个库,欢迎点个 ⭐️ 支持一下!


相关推荐
是一碗螺丝粉1 天前
React Native 运行时深度解析
前端·react native·react.js
努力往上爬de蜗牛3 天前
react native真机调试
javascript·react native·react.js
o***Y3633 天前
鸿蒙NEXT(五):鸿蒙版React Native架构浅析
react native·架构·harmonyos
5***a9753 天前
React Native性能优化技巧
javascript·react native·react.js
wordbaby4 天前
React Native 进阶实战:基于 Server-Driven UI 的动态表单架构设计
前端·react native·react.js
胡琦博客5 天前
21天开源鸿蒙训练营|Day2 ReactNative 开发 OpenHarmony 应用环境搭建实录
javascript·react native·react.js
6***37945 天前
React Native热更新方案
javascript·react native·react.js
x***J3485 天前
React Native组件封装
javascript·react native·react.js
E***U9455 天前
React Native开发
android·react native·react.js