一张适合中文技术文章的手绘笔记风信息图,主题是“HarmonyOS7 可空类型要早处理”。画面中心用

前言

可空类型不是麻烦,它是在提醒你:这个值确实可能不存在。在 HarmonyOS7 的 ArkTS 页面里,如果把 null 一路传到 UI 深处,最后就会出现到处 if、到处兜底的代码。详情页尤其典型,接口还没回来时对象为空,字段缺失时也为空,错误时还是空,如果不早点收敛,build() 会被判断淹没。

我更喜欢在数据进入页面状态前就处理可空值:要么给出默认 UI 模型,要么明确进入空态或错误态。这样 ArkUI 渲染时面对的是确定结构,而不是一堆可能为 null 的字段。

可空值越早处理,UI 越干净;可空值越晚处理,分支越难维护。

为什么这个问题经常被写乱

可空类型要早处理 这类内容很容易被写成“代码能跑就算讲完了”,但对初学者来说,这恰恰是最不够的地方。真正让人卡住的,往往不是某个组件名记不住,而是不知道这段代码为什么要这样拆、状态为什么要这样放、以后需求变化时应该从哪里改。

所以这篇文章不只想给你一个能跑的例子,更想把背后的判断过程讲清楚。你只要把这个判断过程吃透,后面自己改页面、补需求、查问题时,心里会稳很多。

场景:商品详情页

商品详情页通常先展示加载态,再展示详情。如果商品不存在,要显示空态;如果字段缺失,要给出合理兜底。

这里的重点是把“接口真实情况”和“页面渲染需要”分开。接口模型可以诚实地写 string | null,但页面模型最好尽量不可空。否则每个 Text() 前都要问一次字段是否存在,页面会很快失去主线。

处理策略

数据阶段 推荐状态 UI 表现
请求中 loading = true 展示加载文案
请求成功且有数据 detail 非空 展示详情

一张手绘笔记风流程图,内容对应 HarmonyOS7 ArkUI 商品详情页的可空值处理策略。流程从

| 请求成功但无数据 | emptyMessage 非空 | 展示空态 |
| 请求失败 | errorMessage 非空 | 展示错误态 |

先把页面目标想清楚

在真正写代码之前,先别急着盯着 API。更有用的做法是先想清楚:这个页面到底想解决什么问题,用户最在意的反馈是什么,哪些状态必须一直保持一致。

当你先把这条主线想明白,再回头看组件和状态设计,很多选择都会顺理成章。对小白来说,这一步尤其重要,因为它能帮你从“照着抄”慢慢过渡到“看得懂、改得动”。

完整代码示例

interface ProductDetailRaw {
  id: number
  name: string | null
  description: string | null
  price: number | null
  stock: number | null
  tags: string[] | null
}

interface ProductDetailUi {
  id: number
  name: string
  description: string
  priceText: string
  stockText: string
  tagText: string
}

@Entry
@Component
struct NullableDetailPage {
  @State loading: boolean = true
  @State detail: ProductDetailUi | null = null
  @State emptyMessage: string = ''
  @State errorMessage: string = ''

  aboutToAppear(): void {
    this.loadProduct()
  }

  private loadProduct(): void {
    this.loading = true
    this.errorMessage = ''
    this.emptyMessage = ''

    const raw: ProductDetailRaw | null = {
      id: 610,
      name: 'HarmonyOS7 实战课程',
      description: null,
      price: 199,
      stock: 8,
      tags: ['课程', 'ArkUI', '实战']
    }

    if (raw === null) {
      this.detail = null
      this.emptyMessage = '没有找到商品信息'
      this.loading = false
      return
    }

    this.detail = this.toUiModel(raw)
    this.loading = false
  }

  private toUiModel(raw: ProductDetailRaw): ProductDetailUi {
    return {
      id: raw.id,
      name: raw.name === null || raw.name.length === 0 ? '未命名商品' : raw.name,
      description: raw.description === null || raw.description.length === 0 ? '暂无详细介绍' : raw.description,
      priceText: raw.price === null ? '价格待确认' : `${raw.price}`,
      stockText: raw.stock === null ? '库存未知' : `库存 ${raw.stock}`,
      tagText: raw.tags === null || raw.tags.length === 0 ? '暂无标签' : raw.tags.join(' / ')
    }
  }

  build() {
    Column({ space: 18 }) {
      if (this.loading) {
        Text('正在加载商品详情...')
          .fontSize(16)
          .fontColor('#666666')
      } else if (this.errorMessage.length > 0) {
        Text(this.errorMessage)
          .fontSize(16)
          .fontColor('#B3261E')
      } else if (this.emptyMessage.length > 0) {
        Text(this.emptyMessage)
          .fontSize(16)
          .fontColor('#777777')
      } else if (this.detail !== null) {
        Text(this.detail.name)
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .width('100%')

        Text(this.detail.description)
          .fontSize(15)
          .fontColor('#555555')
          .lineHeight(22)
          .width('100%')

        Text(this.detail.tagText)
          .fontSize(13)
          .fontColor('#0A59F7')
          .width('100%')

        Row() {
          Text(this.detail.priceText)
            .fontSize(24)
            .fontWeight(FontWeight.Bold)
            .fontColor('#B42318')
          Blank()
          Text(this.detail.stockText)
            .fontSize(14)
            .fontColor('#666666')
        }
        .width('100%')

        Button('加入学习清单')
          .width('100%')
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .padding(22)
    .backgroundColor('#F6F8FB')
  }
}

一张手绘笔记风框架图,讲清 HarmonyOS7 ArkTS 商品详情页中“原始数据模型”和“页面

关键代码说明

原始模型允许可空。 ProductDetailRaw 贴近接口现实,字段可能是 null 就写出来,不要假装一定存在。

UI 模型尽量不可空。 ProductDetailUi 里的字段都能直接展示,页面不需要再判断 namepricestock 是否为空。

toUiModel() 是可空值收口点。 所有兜底文案都放在这里,后续产品要改“暂无详细介绍”的文案,只改一个位置。

可空处理放在哪里

位置 适合做什么 不适合做什么
接口模型 真实表达字段可能为空 假装字段一定存在
转换方法 兜底、格式化、空态判断 直接操作 UI 组件
页面状态 保存确定的 UI 模型 保存一堆原始可空字段
build() 渲染加载/空/错/内容态 到处写字段兜底

如果字段为空代表不同业务含义,要分别处理。例如 stock = null 是库存未知,stock = 0 是无库存,它们不能都显示成“库存未知”。

不建议的写法

  1. Text() 里到处写三元表达式。 页面会变得难读。
  2. null 当空字符串处理。 语义不一样,空字符串可能是接口给了空值。
  3. 用非空断言逃避判断。 当前能跑,不代表数据永远完整。
  4. 让子组件继续接收可空字段。 可空值应该尽早在上游处理。

build() 面对确定数据

这篇代码的关键点,是让 build() 尽量面对确定的数据结构。接口模型可以诚实表达 name: string | null,但渲染模型里的 name 最好已经是可以直接展示的字符串。

加载态、空态、错误态也不要混在一起。loading 表示还在请求,emptyMessage 表示请求成功但没有数据,errorMessage 表示请求失败。三种状态分清楚,用户看到的反馈才准确,开发排查时也不会把“没数据”和“接口挂了”混成一件事。

如果某个字段为空有业务含义,就不要随手吞掉。比如 stock = 0 是无库存,stock = null 是库存未知,它们应该给用户不同提示。

总结

HarmonyOS7 的 ArkTS 类型检查不是负担,它能逼我们把数据边界想清楚。详情页不要害怕 null,真正要避免的是让 null 在页面里到处流动。

Logo

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

更多推荐