HarmonyOS7 可空类型要早处理:ArkUI/ArkTS 实战拆解

前言
可空类型不是麻烦,它是在提醒你:这个值确实可能不存在。在 HarmonyOS7 的 ArkTS 页面里,如果把 null 一路传到 UI 深处,最后就会出现到处 if、到处兜底的代码。详情页尤其典型,接口还没回来时对象为空,字段缺失时也为空,错误时还是空,如果不早点收敛,build() 会被判断淹没。
我更喜欢在数据进入页面状态前就处理可空值:要么给出默认 UI 模型,要么明确进入空态或错误态。这样 ArkUI 渲染时面对的是确定结构,而不是一堆可能为 null 的字段。
可空值越早处理,UI 越干净;可空值越晚处理,分支越难维护。
为什么这个问题经常被写乱
可空类型要早处理 这类内容很容易被写成“代码能跑就算讲完了”,但对初学者来说,这恰恰是最不够的地方。真正让人卡住的,往往不是某个组件名记不住,而是不知道这段代码为什么要这样拆、状态为什么要这样放、以后需求变化时应该从哪里改。
所以这篇文章不只想给你一个能跑的例子,更想把背后的判断过程讲清楚。你只要把这个判断过程吃透,后面自己改页面、补需求、查问题时,心里会稳很多。
场景:商品详情页
商品详情页通常先展示加载态,再展示详情。如果商品不存在,要显示空态;如果字段缺失,要给出合理兜底。
这里的重点是把“接口真实情况”和“页面渲染需要”分开。接口模型可以诚实地写 string | null,但页面模型最好尽量不可空。否则每个 Text() 前都要问一次字段是否存在,页面会很快失去主线。
处理策略
| 数据阶段 | 推荐状态 | UI 表现 |
|---|---|---|
| 请求中 | loading = true |
展示加载文案 |
| 请求成功且有数据 | detail 非空 |
展示详情 |

| 请求成功但无数据 | 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')
}
}

关键代码说明
原始模型允许可空。 ProductDetailRaw 贴近接口现实,字段可能是 null 就写出来,不要假装一定存在。
UI 模型尽量不可空。 ProductDetailUi 里的字段都能直接展示,页面不需要再判断 name、price、stock 是否为空。
toUiModel() 是可空值收口点。 所有兜底文案都放在这里,后续产品要改“暂无详细介绍”的文案,只改一个位置。
可空处理放在哪里
| 位置 | 适合做什么 | 不适合做什么 |
|---|---|---|
| 接口模型 | 真实表达字段可能为空 | 假装字段一定存在 |
| 转换方法 | 兜底、格式化、空态判断 | 直接操作 UI 组件 |
| 页面状态 | 保存确定的 UI 模型 | 保存一堆原始可空字段 |
build() |
渲染加载/空/错/内容态 | 到处写字段兜底 |
如果字段为空代表不同业务含义,要分别处理。例如 stock = null 是库存未知,stock = 0 是无库存,它们不能都显示成“库存未知”。
不建议的写法
- 在
Text()里到处写三元表达式。 页面会变得难读。 - 把
null当空字符串处理。 语义不一样,空字符串可能是接口给了空值。 - 用非空断言逃避判断。 当前能跑,不代表数据永远完整。
- 让子组件继续接收可空字段。 可空值应该尽早在上游处理。
让 build() 面对确定数据
这篇代码的关键点,是让 build() 尽量面对确定的数据结构。接口模型可以诚实表达 name: string | null,但渲染模型里的 name 最好已经是可以直接展示的字符串。
加载态、空态、错误态也不要混在一起。loading 表示还在请求,emptyMessage 表示请求成功但没有数据,errorMessage 表示请求失败。三种状态分清楚,用户看到的反馈才准确,开发排查时也不会把“没数据”和“接口挂了”混成一件事。
如果某个字段为空有业务含义,就不要随手吞掉。比如 stock = 0 是无库存,stock = null 是库存未知,它们应该给用户不同提示。
总结
HarmonyOS7 的 ArkTS 类型检查不是负担,它能逼我们把数据边界想清楚。详情页不要害怕 null,真正要避免的是让 null 在页面里到处流动。
更多推荐

所有评论(0)