一、前言
很多刚开始学习 HarmonyOS 的同学都会遇到一个问题:
例如:
class LoginRepository { login(){ // 登录失败 // 想直接弹个Dialog } }结果发现:
Cannot find UIContext 或者 PromptAction is undefined为什么?
因为:
HarmonyOS 的所有弹窗,本质都是依赖 UIContext。
而:
Repository Manager Util Http SDK这些都属于:
逻辑层(Non-UI)
它们根本不知道:
当前页面是谁? 当前Window是谁? 当前Ability是谁?所以:
逻辑层不能直接弹窗。
二、官方推荐方式
官方已经说明:
PromptAction:
依赖 UI 执行上下文推荐:
UIContext.getPromptAction()而不是:
promptAction.showDialog()因为:
UIContext ↓ PromptAction ↓ Window ↓ Ability ↓ 当前页面这样生命周期才一致。
三、为什么逻辑类没有UIContext
例如:
Entry ↓ HomePage ↓ LoginPage ↓ Button ↓ onClick() ↓ LoginManager.login()LoginManager:
class LoginManager{ }这里:
this.getUIContext()根本不存在。
因为:
只有Component 只有Ability 才拥有UIContext四、错误示例
很多人会这样写:
import promptAction from '@ohos.promptAction' class LoginManager{ login(){ promptAction.openCustomDialog() } }运行:
UIContext不存在 无法打开Dialog原因:
promptAction 没有Window 没有UI 没有Context五、正确思路
整个工程应该变成:
UI ↓ 获得UIContext ↓ 保存UIContext ↓ DialogManager ↓ LoginManager ↓ 任何地方调用即:
Repository ↓ DialogManager.show() ↓ UIContext ↓ PromptAction六、第一步:保存UIContext
例如:
EntryAbility初始化:
onWindowStageCreate(windowStage: window.WindowStage) { windowStage.loadContent('pages/Index', () => { window.getLastWindow(this.context).then(win=>{ let uiContext = win.getUIContext() }) }) }然后保存:
AppStorage.setOrCreate( "UIContext", uiContext )这样:
整个应用:
AppStorage ↓ UIContext任何地方都能拿到。
七、封装ContextManager
例如:
export class ContextManager{ static getUIContext():UIContext{ return AppStorage.get<UIContext>("UIContext")! } }以后:
ContextManager.getUIContext()即可。
八、封装DialogManager
推荐所有项目都建立:
common DialogManager.ts例如:
export class DialogManager{ }以后:
Toast Loading Dialog Sheet Popup 全部放这里统一管理。
九、封装showDialog()
例如:
static show(){ let ui = ContextManager.getUIContext() ui.getPromptAction() }这里:
UIContext ↓ PromptAction ↓ Dialog即可打开弹窗。
十、为什么推荐PromptAction
HarmonyOS目前主要有两种弹窗方式:
CustomDialogController和
PromptAction.openCustomDialog()其中官方更推荐基于UIContext + PromptAction的方案,因为它与页面生命周期解耦,更适合工程化封装和在逻辑层统一调用。
十一、企业级架构
大型项目通常采用如下结构:
UI │ ├── Page ├── Dialog ├── Sheet └── Popup Business │ ├── LoginManager ├── UserManager ├── PayManager └── OrderManager Infrastructure │ ├── DialogManager ├── ToastManager ├── LoadingManager └── ContextManager业务层永远不关心弹窗如何实现:
LoginManager.login()失败:
DialogManager.showError("账号不存在")即可。
十二、统一弹窗接口
建议定义统一能力:
DialogManager.showInfo() DialogManager.showSuccess() DialogManager.showError() DialogManager.showWarning() DialogManager.showLoading() DialogManager.dismissLoading() DialogManager.showConfirm()业务代码变得非常简单:
await login() DialogManager.showSuccess("登录成功")十三、异步确认框
例如:
const result = await DialogManager.confirm("确定删除吗?")返回:
true false业务代码:
if(result){ delete() }比传统回调更加清晰。
十四、避免重复弹窗
建议增加状态控制:
private static showing = false打开前判断:
if(DialogManager.showing){ return }关闭:
DialogManager.showing=false可以避免用户连续点击造成多个弹窗叠加。
十五、弹窗队列
企业项目经常会遇到:
登录失效 ↓ 网络异常 ↓ 版本更新 ↓ 支付结果多个弹窗同时出现。
推荐维护:
Queue ↓ show() ↓ poll() ↓ dismiss() ↓ next()保证:
一次只显示一个Dialog用户体验更好。
十六、生命周期处理
页面关闭时:
Page disappear ↓ Dialog dismiss ↓ 释放 ComponentContent ↓ 释放引用避免:
- Window 泄漏
- UIContext 持有旧页面
- 重复创建 Builder
特别是在使用openCustomDialog时,应在关闭后及时释放对应资源。
十七、适用场景
这种全局弹窗方案尤其适用于:
- Repository 登录失败提示
- 网络请求异常
- Token 失效重新登录
- 支付结果提示
- BLE 连接失败
- WebSocket 断开提示
- 下载进度 Loading
- 全局版本更新提示
- 权限申请说明
- 风险操作确认
十八、最佳实践总结
经过大量 HarmonyOS 项目实践,推荐遵循以下原则:
- 不要在业务逻辑类直接依赖 UI 组件。
- 统一保存并管理
UIContext。 - 优先使用
UIContext.getPromptAction()实现弹窗。 - 封装
DialogManager,所有弹窗统一入口。 - 支持 Promise 化确认框,提升业务代码可读性。
- 增加弹窗队列和重复弹窗拦截机制。
- 页面销毁时及时释放弹窗资源,避免内存泄漏。
这样可以实现真正的:
- UI 与业务解耦
- 逻辑层一行代码弹窗
- 统一样式、统一动画、统一生命周期管理
- 适用于中大型 HarmonyOS 企业项目。