本篇看点
- 阅读目标:用明确状态模型表达 loading、error、empty、content,减少散乱 boolean。
- 核心问题:网络页面先建状态模型,再渲染 UI,而不是请求成功后直接塞数组。
- 对比迁移:对应 Flutter AsyncValue/Bloc State 和 iOS ViewState/Result。
前言
网络列表不是"请求成功后展示数组"这么简单。真实页面至少有四态:loading、error、empty、content。少任何一个,用户体验都会不完整。
Flutter 里你可能会用 AsyncSnapshot、Bloc state、Riverpod 的 AsyncValue。iOS 里你可能会建 ViewState 或 Result。HarmonyOS 里也应该先建状态模型,再写 UI。
一. 先定义页面状态
页面里堆几个布尔值时,很快会出现状态组合问题:
ts
@State loading: boolean = false;
@State failed: boolean = false;
@State empty: boolean = false;
这种写法很容易出现互相矛盾:loading 和 failed 同时为 true,或者 items 有数据但 empty 还是 true。
更好的方式是用状态对象:
ts
enum LoadStatus {
Loading,
Success,
Empty,
Failure
}
interface PageLoadState<T> {
status: LoadStatus;
data?: T;
message?: string;
}
页面只根据 status 渲染:
ts
if (this.state.status === LoadStatus.Loading) {
LoadingView()
} else if (this.state.status === LoadStatus.Failure) {
ErrorView({ message: this.state.message })
} else if (this.state.status === LoadStatus.Empty) {
EmptyView()
} else {
ContentList({ items: this.state.data ?? [] })
}
二. Repository 返回 Result,而不是直接抛给页面
网络请求失败可能来自很多地方:无网、超时、服务端错误、解析失败、业务错误码。页面更适合消费整理后的错误状态,而不是直接理解所有底层细节。
ts
interface Result<T> {
success: boolean;
data?: T;
message?: string;
code?: string;
}
Repository 负责把底层错误统一成业务可理解的结果:
ts
async loadArticles(): Promise<Result<Article[]>> {
try {
const response = await this.http.get<Article[]>('/articles');
return { success: true, data: response };
} catch (error) {
return { success: false, message: '网络请求失败,请稍后重试' };
}
}
这和 iOS 的 Result<T, Error>、Flutter 里的 Either/Result 是同一类工程习惯。
三. loading 分两种
首屏 loading 和局部 loading 可以分开建模。
首屏没有内容时,可以展示骨架屏或空白 loading。已有内容时刷新,更适合保留内容并展示局部刷新状态,否则体验会闪。
ts
interface ListState<T> {
items: T[];
initialLoading: boolean;
refreshing: boolean;
loadingMore: boolean;
errorMessage?: string;
}
资讯列表、发现分页、排行榜切 Tab,都可以用这种状态结构。
四. error 也要分层
错误页不是只有一个"请求失败"。至少分:
- 首屏失败:页面没有内容,需要占位和重试。
- 刷新失败:保留旧内容,提示刷新失败。
- 加载更多失败:底部显示重试,不影响已有列表。
- 详情失败:详情页展示错误和返回能力。
Flutter 中也常这样拆,比如 status == initialFailure 和 status == loadMoreFailure;iOS 里会区分 empty view、toast、footer retry。
五. 为什么这件事适合做成练习
初学网络时,如果只写一个 fetch(),学习者会误以为网络就是 API 调用。真正有价值的练习,是让读者看到状态如何驱动 UI。
在练习页面里,网络列表可以模拟慢请求、失败、空数据、重试。到了真实 App,同样模型可以迁移到资讯列表、专辑详情和发现页分页。
六. 实践经验
第一,用多个 boolean 管页面状态,状态组合失控。
第二,首屏 loading 和刷新 loading 用同一种遮罩,页面一直闪。
第三,网络错误直接展示底层异常,用户看不懂。
第四,Repository 把 UI 文案写死,导致不同页面无法定制错误展示。
小结
网络请求的第一课不是 GET/POST,而是四态建模。只要状态设计清楚,UI、重试、缓存、分页都会顺很多。
下一篇我们继续网络列表,重点讲下拉刷新、上拉加载、竞态保护和分页边界。
今日练习
- 把一个网络页面改成
Loading/Success/Empty/Failure四态。 - 让首屏失败展示错误页,刷新失败只提示,不清空旧内容。
- 给 Repository 返回统一
Result<T>,页面不直接处理底层异常。