)
摘要项目列表页面普遍存在 4 种状态加载中、空数据、网络错误、正常列表。每个页面重复编写状态判断、Loading 动画、空视图、错误重试按钮冗余代码极多。封装一套全局通用状态兜底组件StateView统一承载加载、空数据、网络异常、正常内容四种视图支持自定义文案、重试回调、图片、动画所有列表页面直接复用大幅减少页面重复布局代码。API23 优化组件状态刷新渲染、组件销毁资源回收、动画自动释放逻辑修复状态切换闪烁、多次重复渲染、加载动画后台持续运行耗电等问题。关键词OpenHarmonyArkUI通用状态组件空页面加载视图错误重试状态封装复用组件一、引言1.1 页面状态开发痛点每个列表页面重复写State isLoading / isEmpty / isError状态变量加载动画、空白提示、错误重试布局到处复制粘贴样式不统一网络出错页面无重试按钮用户需手动返回刷新状态切换时页面布局抖动、闪烁无平滑过渡页面销毁后加载动画未终止持续占用 CPU不同页面空页面文字、图标不统一UI 视觉割裂。1.2 页面四种标准业务状态加载中 Loading接口请求未返回展示旋转加载动画 提示文字空数据 Empty接口正常返回列表长度为 0展示空图标 提示文案网络 / 业务错误 Error接口报错、超时、500展示错误图标 重试按钮正常 Content数据正常返回渲染业务列表 / 卡片API23 组件渲染核心升级BuilderParam参数缓存优化状态切换仅局部刷新不重绘整个页面内置动画生命周期监听组件销毁自动停止循环旋转动画条件渲染分支优化避免多状态同时渲染造成布局重叠支持全局默认样式统一配置单页面可局部覆盖自定义参数。二、通用状态组件封装 components/common/StateView.etsetsComponent export struct StateView { // 页面当前状态枚举 Param state: PageState PageState.LOADING // 自定义文字 Param loadingText: string 数据加载中... Param emptyText: string 暂无数据 Param errorText: string 加载失败请点击重试 // 重试回调错误页面点击执行刷新 Param onRetry: () void () {} // 正常业务内容插槽 BuilderParam contentBuilder: () void // 旋转动画状态 State rotateAngle: number 0 aboutToAppear() { // 加载状态开启无限旋转动画 animateTo({ duration: 1200, iterations: Infinity, curve: Curve.Linear }, () { this.rotateAngle 360 }) } aboutToDisappear() { // 组件销毁重置角度终止循环动画 this.rotateAngle 0 } // 加载视图 Builder LoadingView() { Column({ space: 16 }) { Text(⟳) .fontSize(42) .rotate({ angle: this.rotateAngle }) .animation({ duration: 1200, iterations: Infinity, curve: Curve.Linear }) Text(this.loadingText) .fontSize(16) .fontColor(#999999) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) } // 空数据视图 Builder EmptyView() { Column({ space: 16 }) { Image($r(sys.media.ohos_ic_public_empty)) .width(80) .height(80) .fillColor(#cccccc) Text(this.emptyText) .fontSize(16) .fontColor(#999999) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) } // 错误重试视图 Builder ErrorView() { Column({ space: 20 }) { Image($r(sys.media.ohos_ic_public_fail)) .width(80) .height(80) .fillColor(#cccccc) Text(this.errorText) .fontSize(16) .fontColor(#999999) Button(点击重试) .width(140) .height(44) .backgroundColor(#007DFF) .onClick(this.onRetry) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) } build() { Column() { if (this.state PageState.LOADING) { this.LoadingView() } else if (this.state PageState.EMPTY) { this.EmptyView() } else if (this.state PageState.ERROR) { this.ErrorView() } else { // 正常业务列表内容 this.contentBuilder() } } .width(100%) .layoutWeight(1) } } // 页面状态枚举全局统一管理 export enum PageState { LOADING, EMPTY, ERROR, CONTENT }三、列表页面实战完整调用笔记列表页改造etsimport StateView, { PageState } from ../components/common/StateView import RdbUtil, { Note } from ../utils/rdb_util import LogUtil from ../utils/log_util Entry Component struct NoteListPage { State pageState: PageState PageState.LOADING State noteList: Note[] [] async aboutToAppear() { RdbUtil.setContext(getContext(this)) await this.loadData() } // 统一刷新数据方法重试按钮复用 async loadData() { this.pageState PageState.LOADING try { const list await RdbUtil.queryNoteList(0, 20) this.noteList list if (list.length 0) { this.pageState PageState.EMPTY } else { this.pageState PageState.CONTENT } LogUtil.info(NoteList, 笔记数据加载完成条数, list.length) } catch (err) LogUtil.error(NoteList, 数据库查询失败, err) this.pageState PageState.ERROR } } build() { Column({ space: 12 }) { Row() { Text(我的笔记).fontSize(22).layoutWeight(1) Button(新增笔记).backgroundColor(#007DFF) } .width(95%) // 通用状态容器传入状态、重试回调、业务列表插槽 StateView({ state: this.pageState, onRetry: () this.loadData() }) { // 正常业务列表内容插槽 List({ space: 10 }) { ForEach(this.noteList, (item: Note) { ListItem() { Row() { Column().layoutWeight(1) { Text(item.title).fontSize(17) Text(item.content).fontColor(#666) } Button(删除).backgroundColor(#f56c6c) } .width(100%) .padding(16) .backgroundColor(Color.White) .borderRadius(10) } }) } .width(95%) } } .width(100%) .height(100%) .padding(12) .backgroundColor(#F5F5F5) } async aboutToDisappear() { await RdbUtil.closeDB() } }四、自定义文案局部覆盖示例资讯页面etsStateView({ state: this.pageState, loadingText: 资讯拼命加载中..., emptyText: 暂无资讯内容, errorText: 网络开小差了点击重新加载, onRetry: () this.refreshNews() }) { // 资讯列表业务布局 List() { ... } }五、通用状态组件开发编码规范5.1 状态变量规范所有列表页面统一使用State pageState: PageState禁止自定义 isLoading、isEmpty 零散变量数据请求开始强制赋值PageState.LOADING请求成功判断数组长度0 条切换 EMPTY有数据切换 CONTENT请求捕获异常统一切换 ERROR。5.2 组件复用规范所有列表、分页、接口页面统一使用 StateView 包裹业务列表全局默认文字统一写在组件入参默认值统一产品 UI 风格单页面特殊场景仅局部传参覆盖文字不重复新建空页面布局。5.3 动画生命周期规范组件内置 aboutToDisappear 重置旋转角度终止无限加载动画禁止页面内单独写加载动画统一复用组件内置动画。5.4 重试逻辑规范数据加载逻辑抽离独立loadData方法页面初始化、重试按钮共用错误页面点击重试自动执行完整刷新流程无需重复编写请求代码。5.5 性能渲染规范使用BuilderParam插槽仅切换状态时局部重渲染不刷新页面其他控件四种视图互斥渲染同一时间仅显示一种避免多层组件重叠占用渲染资源。六、高频问题与解决方案问题 1页面退出后加载旋转动画持续后台运行 解决组件内部 aboutToDisappear 重置 rotateAngle 终止无限循环动画。问题 2接口返回空列表页面同时显示加载和空视图 解决请求完成后同步更新 pageState互斥 if 分支只会渲染单一视图。问题 3多个页面空页面图标、文字不统一 解决统一在 StateView 设置默认参数全局一套 UI 标准特殊页面单独覆盖文字。问题 4点击重试重复发起多次请求 解决页面增加请求 loading 锁请求期间拦截重复点击按钮。问题 5状态切换页面明显抖动、布局偏移 解决四种视图统一设置宽高 100% 居中固定占位区域布局尺寸无变化。七、总结StateView 通用状态兜底组件统一封装加载、空数据、网络错误三大兜底页面通过枚举统一管理页面业务状态利用 Builder 插槽实现业务布局与状态视图解耦一套组件适配项目所有列表、资讯、笔记、商品页面消除大量重复布局代码统一 APP 全局空 / 错误 / 加载 UI 风格。 完全兼容前文 RDB、Http 网络请求、日志工具配套使用API23 优化组件渲染与动画回收是企业级鸿蒙项目通用基础业务组件可直接整合进整套 HAR 分层架构。