弹窗、半模态与页面状态配合

很多页面第一版都不难。照着设计稿写几个 Row、Column,再把按钮事件接上,模拟器里一跑,看起来已经差不多了。真正麻烦的是第二轮需求:这里加筛选,那里加 loading,再补一个空状态,产品又说横向空间不够。页面很快就从“能跑”变成“不敢动”。

这一篇我们就处理一个很具体的问题:弹窗、半模态与页面状态配合。不堆 API,也不为了显得高级去造框架。先把问题拆开,再写一个能运行的最小版本,最后补上真实项目里最容易漏掉的边界。

在这里插入图片描述

1. 先别急着写代码,看看问题到底在哪

ArkUI 是声明式 UI。这个词听着有点抽象,其实可以理解成一句话:页面应该描述“当前状态下长什么样”,而不是到处写命令去手工改界面。

很多页面后期难维护,不是因为 ArkTS 难,而是三个东西混在了一起:页面结构、业务状态、交互流程。一个点击事件里既改变量,又发请求,又拼参数,还顺手控制弹窗,短期很快,后面排错就很痛苦。

我写业务页时通常先画三个框:页面有什么区域;哪些数据会变化;变化由谁触发。十分钟想清楚,往往比直接敲一小时代码更省时间。

2. 先做最小可运行版本

下面这段代码只保留本篇最核心的结构:

@State showDetail: boolean = false

Button('查看详情').onClick(() => {
  this.showDetail = true
})

if (this.showDetail) {
  DetailPanel({
    onClose: () => {
      this.showDetail = false
    }
  })
}

这里不要急着抽象。先在 DevEco Studio 里跑起来,看模拟器上的行为是否符合预期。教程里最容易出现的问题,就是代码看着“架构很好”,结果复制进工程缺一堆上下文。我们反过来,先保证核心链路成立。

3. 为什么这样写会更稳

关键不是少写几行,而是职责清楚。页面知道自己当前是什么状态,组件知道自己负责显示什么,事件只负责触发变化。

例如用户点一次按钮,如果背后有异步请求,页面至少要知道“正在请求”。否则连续点三次,就可能真的发三次请求。再比如列表刷新时,如果刷新和加载更多共用一个模糊的 loading,很容易出现底部正在加载时整个页面突然被遮住。

状态命名也别太随意。flag1、isShow2 这种变量写的时候快,两周后自己都不知道是什么意思。宁愿长一点,也写成 refreshing、showFilterPanel、pageStatus。

4. 把正常路径和异常路径一起写

只写成功流程,是 Demo;把失败、空数据和恢复流程补齐,才接近项目。

异步方法我一般会保留完整的 try / catch / finally。try 负责正常请求,catch 决定失败后页面怎么表现,finally 则非常适合恢复 loading。这样即使请求抛异常,也不至于让按钮永远停在“处理中”。

async loadData() {
  if (this.loading) return
  this.loading = true

  try {
    const data = await this.requestData()
    this.items = data
  } catch (err) {
    console.error('[DialogPage] loadData failed')
  } finally {
    this.loading = false
  }
}

注意第一行的防重复判断。它看起来不起眼,但在真实业务里特别有用。

5. UI 状态不要越堆越多

另一个常见问题是 boolean 越写越多:

@State loading: boolean = false
@State empty: boolean = false
@State error: boolean = false
@State success: boolean = false

这四个变量理论上能组合出很多状态,但业务真正允许的通常只有几个。万一 loading 和 error 同时变成 true,到底显示谁?

互斥状态更适合收口成枚举:

enum PageStatus {
  Idle,
  Loading,
  Success,
  Empty,
  Error
}

@State pageStatus: PageStatus = PageStatus.Idle

这样页面在某一时刻只处于一个明确状态,阅读和排错都会轻松很多。

6. 组件到底什么时候拆

不是代码超过 100 行就必须拆。更实用的判断有三个:这个区域有没有独立职责?有没有自己的局部状态?以后会不会复用?

满足其中一个,就值得考虑拆组件。反过来,一个只有两行文字、没有状态、也不会复用的区域,硬拆成单独文件反而增加阅读成本。

我更习惯把页面看成“组织者”。页面负责把标题区、筛选区、内容区组合起来,真正的细节交给业务组件。网络请求、数据转换、持久化则尽量不要全部塞在页面文件里。

7. DevEco Studio 里怎么排查“页面不对”

遇到 UI 没变化,先别怀疑框架。按这个顺序查:

第一步,看点击事件有没有触发。第二步,看状态值是不是真的改了。第三步,看当前 UI 是否依赖这个状态。第四步,如果有异步逻辑,看异常是不是提前打断了后面的代码。

日志建议带上页面名:

console.info('[DialogPage] start')
console.info('[DialogPage] status changed')

别一次打印一个巨大对象。真正出问题时,日志越多不一定越容易看。

8. 再往真实业务靠一步

弹窗、半模态与页面状态配合放到真实项目里,通常还会碰到页面重新进入、窗口尺寸变化、接口慢、用户连续操作、数据为空等情况。

所以写完“正常效果”以后,我会故意做几次破坏性测试:断网再点一次;连续点五次;让接口返回空数组;切到别的页面再回来;把文案改得特别长。很多布局和状态问题,这时候才会暴露。

9. 不要为了工程化而工程化

项目里还有一种反方向的问题:刚写两个页面,就开始设计万能基类、万能弹窗、万能列表。

抽象应该来自重复,而不是来自想象。业务还没稳定时,先写清楚通常比先写通用更重要。等两三个页面真的出现相同结构,再抽公共组件,边界会准确很多。

资源也是一样。会重复出现的颜色、尺寸、文案可以逐步收口;只出现一次的东西没必要第一天就建立十层配置。

10. 提交前做一次快速自检

我一般会检查这些事情:首次进入是否正常;连续点击是否重复请求;loading 能不能恢复;空数据有没有页面;失败后能不能重试;长文本会不会顶坏布局;列表数据多起来以后是否明显卡顿;页面重新进入后状态是不是预期值。

这些检查没有炫技成分,却非常接近线上真正会出现的问题。

最后总结

这一篇真正要练的不是记 API,而是形成一个稳定顺序:先拆页面结构,再确定状态归属,然后写交互,最后补异常路径。

HarmonyOS 7 的 ArkUI 本身并不难,页面越写越乱,通常是因为结构和状态没有先想清楚。把这个顺序养成习惯,后面无论写列表、表单、Tabs 还是复杂业务页,都会顺很多。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐