HarmonyOS7 网络错误提示别只弹 Toast:失败状态要留在页面上
前言
网络失败只弹一个 Toast,看起来省事,真实项目里很容易坑用户。Toast 消失太快,用户没看清;列表又没有内容,他也不知道是网络断了、接口挂了,还是页面本来就空。
我现在写 HarmonyOS7 页面时,会把网络错误放进页面状态里。Toast 可以作为补充,但主反馈应该留在页面上:错误说明、重试按钮、旧数据保留、上次更新时间。只要失败会影响页面内容,就不要只靠 Toast。

为什么这个地方最容易做得很敷衍
因为弹一个 Toast 实在太快了。代码少、效果也立刻能看到,所以很多初学者会下意识觉得“提示已经有了,这事就算做完了”。
但网络错误不是一个轻飘飘的提醒,它会直接影响用户还能不能继续用这个页面。列表没出来、数据没更新、按钮点了没反应,这些都不是一个两秒钟消失的小提示能交代清楚的。
所以这篇文章真正想解决的,不是“怎么弹提示”,而是“失败之后页面到底该怎么接住用户”。
先把错误分层
网络错误不是一个统一状态。首次加载失败和刷新失败,页面处理方式完全不同。
| 场景 | 页面反馈 | 用户动作 |
|---|---|---|
| 首次加载失败 | 显示错误面板 | 点击重试 |
| 已有数据刷新失败 | 保留旧数据,顶部提示 | 稍后再试 |
| 接口返回空数据 | 显示空状态 | 去创建或调整条件 |
| 请求中 | 展示加载态 | 等待 |
这几个状态不能全都写成“加载失败”。用户看到的不是技术原因,而是下一步能不能继续操作。

先把页面目标想清楚
在真正写代码之前,先别急着盯着 API。更有用的做法是先想清楚:请求失败之后,用户眼前应该剩下什么,是整个页面报错,还是保留旧数据继续可用。
像成员列表这种页面,用户最在意的通常不是“到底抛了什么异常”,而是“我现在还能不能继续看内容,能不能重试”。所以状态设计时,应该优先围绕这两个问题来拆。
对小白来说,这一步能帮你从“catch 里塞一句提示”进阶到“页面状态怎么跟业务动作对应”。
完整 ArkTS 示例
下面示例是一个成员列表。首次失败时显示错误面板;如果已经有数据,再刷新失败时保留旧列表,只在顶部提示。
import { http } from '@kit.NetworkKit'
import { promptAction } from '@kit.ArkUI'
interface UserItem21 {
id: number
name: string
role: string
}
@Entry
@Component
struct NetworkErrorPage {
@State loading: boolean = false
@State errorMessage: string = ''
@State lastUpdate: string = '尚未加载'
@State users: UserItem21[] = []
aboutToAppear(): void {
this.loadUsers()
}
async loadUsers(): Promise<void> {
if (this.loading) {
return
}
this.loading = true
this.errorMessage = ''
const request = http.createHttp()
try {
const result = await request.request('https://example.com/api/users', {
method: http.RequestMethod.GET,
connectTimeout: 6000,
readTimeout: 6000
})
if (result.responseCode >= 200 && result.responseCode < 300) {
this.users = [
{ id: 1, name: '林澈', role: '产品经理' },
{ id: 2, name: '周南', role: 'ArkTS 开发' },
{ id: 3, name: '陈予', role: '测试工程师' }
]
this.lastUpdate = '刚刚更新'
} else {
this.errorMessage = `服务暂时不可用,状态码 ${result.responseCode}`
}
} catch (err) {
this.errorMessage = '网络连接失败,请检查网络后重试'
promptAction.showToast({ message: '加载失败,页面已保留错误信息' })
} finally {
request.destroy()
this.loading = false
}
}
@Builder
ErrorPanel() {
Column({ space: 12 }) {
Text('没有加载成功')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text(this.errorMessage)
.fontSize(14)
.fontColor('#666666')
.textAlign(TextAlign.Center)
Button('重新加载')
.onClick(() => this.loadUsers())
}
.width('100%')
.padding(24)
.backgroundColor('#FFF7F5')
.borderRadius(12)
}
build() {
Column({ space: 16 }) {
Row() {
Column() {
Text('成员列表').fontSize(24).fontWeight(FontWeight.Bold)
Text(`更新时间:${this.lastUpdate}`).fontSize(12).fontColor('#888888')
}
.alignItems(HorizontalAlign.Start)
Blank()
Button(this.loading ? '加载中' : '刷新')
.enabled(!this.loading)
.onClick(() => this.loadUsers())
}
.width('100%')
if (this.errorMessage.length > 0 && this.users.length === 0) {
this.ErrorPanel()
} else {
if (this.errorMessage.length > 0) {
Text(this.errorMessage)
.fontSize(13)
.fontColor('#B3261E')
.padding(10)
.width('100%')
.backgroundColor('#FFF0EE')
.borderRadius(8)
}
List({ space: 10 }) {
ForEach(this.users, (item: UserItem21) => {
ListItem() {
Row() {
Column() {
Text(item.name).fontSize(17).fontWeight(FontWeight.Medium)
Text(item.role).fontSize(13).fontColor('#777777')
}
.alignItems(HorizontalAlign.Start)
Blank()
Text(`#${item.id}`).fontSize(12).fontColor('#999999')
}
.width('100%')
.padding(14)
.backgroundColor(Color.White)
.borderRadius(10)
}
}, (item: UserItem21) => item.id.toString())
}
.layoutWeight(1)
}
}
.padding(16)
.width('100%')
.height('100%')
.backgroundColor('#F6F7F9')
}
}

把关键代码一段段拆开
errorMessage 是页面状态,不是 catch 里的临时变量。只要错误影响 UI,就应该进入 @State,这样重试、保留旧数据、顶部提示都能统一驱动。
this.errorMessage.length > 0 && this.users.length === 0 用来判断首次失败。没有旧数据时要给完整错误面板;有旧数据时,不要把列表清空,只需要在顶部提示刷新失败。
finally 里调用 request.destroy()。请求成功、失败、状态码异常都会走这里,资源释放不会漏。
业务里还要补两层保护
第一层是错误来源。超时、服务端异常、登录过期、无网络,给用户看的文案不应该完全一样。
| 错误来源 | 用户文案 | 页面动作 |
|---|---|---|
| 超时 | 当前网络较慢,请稍后重试 | 保留重试按钮 |
| 服务端异常 | 服务暂时不可用 | 展示状态码或问题编号 |
| 登录过期 | 登录状态已失效 | 引导重新登录 |
| 无网络 | 请检查网络连接 | 保留旧数据 |
第二层是重试节奏。请求期间按钮要禁用,弱网下连续点重试很容易发出多次请求,后返回的旧结果还可能覆盖新结果。
新手最容易踩的坑
第一个坑,是一失败就把列表清空。这样用户上一秒还能看到内容,下一秒页面直接空掉,体验会很差。
第二个坑,是把所有失败都写成同一句文案。超时、无网络、登录过期、服务端异常,虽然都叫“失败”,但用户下一步该做的事并不一样。
第三个坑,是只在 catch 里弹提示,却没有把失败状态放进页面。等 Toast 消失之后,用户就不知道自己该干嘛了。
放进真实项目还要补什么
真实项目里,错误提示通常还要和埋点、日志、登录态、重试策略一起考虑。比如登录过期要不要直接跳登录页,弱网重试要不要做节流,这些都不是单靠一个提示文案能解决的。
如果页面已经有旧数据,很多时候更稳的做法是“保留旧内容 + 顶部提示刷新失败 + 给用户重试入口”。这比直接把内容全抹掉,更符合真实使用场景。
写在最后
Toast 适合提醒,不适合兜底。只要网络失败会影响页面主内容,你就应该把错误状态留在页面上,让用户看得见、点得动、知道下一步该做什么。
更多推荐

所有评论(0)