第1.3篇:@State 与状态管理------让页面"活"起来
系列 :HarmonyOS 从入门到实践 · 画伴梦工厂实战
难度 :⭐⭐ 进阶
前置知识 :1.2 ArkUI 声明式 UI 基础
涉及源文件 :
products/default/src/main/ets/pages/Index.ets、products/default/src/main/ets/pages/RecognitionWaitingPage.ets

在上一篇中,我们学习了如何用 ArkUI 组件搭建静态页面。但真实的 App 需要响应交互、更新数据、刷新 UI。这就要靠 ArkUI 的状态管理机制来实现了。
ArkUI 最核心的设计理念是:UI 是状态(State)的函数。当状态变化时,框架自动重新渲染受影响的 UI 部分,开发者无需手动操作 DOM。
一、@State 装饰器:响应式数据绑定
@State 是 ArkUI 中最基础的响应式装饰器。被 @State 修饰的变量一旦赋值发生变化,所有依赖该变量的 UI 组件都会自动刷新。
语法规则
typescript
@State 变量名: 类型 = 初始值;
来看"画伴梦工厂"首页 Index 组件中声明的 @State 变量:
typescript
@Entry
@Component
struct Index {
// 响应式状态变量
@State private currentTab: number = 0; // 当前选中的Tab
@State private secondaryPage: number = SECONDARY_NONE; // 二级页面状态
@State private generationProgress: number = 68; // 生成进度
@State private isPlaying: boolean = true; // 视频播放状态
@State private chatInput: string = ''; // 聊天输入框内容
@State private chatMessages: ChatMessage[] = [ // 聊天消息列表
{ id: 1, role: 'assistant', text: '说出你想创作的角色和场景...', imageUri: '' }
];
@State private noticeText: string = ''; // 通知文本
@State private animationFrame: number = 0; // 动画帧
@State private selectedCreationMode: number = 0; // 选中的创作模式
// ... 更多 @State 变量
}
可以看到,@State 可以修饰多种数据类型:
| 类型 | 示例 | 说明 |
|---|---|---|
number |
currentTab: number = 0 |
数值,用于索引、进度等 |
boolean |
isPlaying: boolean = true |
布尔值,控制显隐、状态切换 |
string |
chatInput: string = '' |
字符串,文本内容 |
| 自定义接口数组 | chatMessages: ChatMessage[] |
数组,增删元素触发 UI 刷新 |
| 枚举数值 | secondaryPage: number |
通过常量值枚举页面状态 |
二、状态驱动 UI 变化
2.1 Progress 进度条驱动
在 RecognitionWaitingPage 中,@State progress 的变化会实时驱动 Progress 组件的显示:
typescript
@State private progress: number = 12;
build() {
Column() {
// 进度百分比文本
Text(this.progress.toString() + '%')
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor(this.brandPurple)
// 进度条------随 progress 变化自动刷新
Progress({ value: this.progress, total: 100, type: ProgressType.Linear })
.width('84%')
.height(8)
.color(this.mint)
.backgroundColor('#ECECF6')
.borderRadius(6)
}
}
Progress({ value: this.progress }) 直接绑定到 @State progress。当 progress 从 12 逐渐增加到 100,进度条和百分比文本会同步更新,无需任何手动刷新代码。
2.2 文本和按钮状态联动
同一个页面的状态变量还会联动影响多个 UI 元素:
typescript
@State private statusText: string = '正在准备生成任务';
@State private failed: boolean = false;
@State private completed: boolean = false;
build() {
Column() {
// 状态文本------颜色随 failed 变化
Text(this.statusText)
.fontColor(this.failed ? '#D94C3D' : '#68708A')
// 按钮------文字和颜色随 completed/failed 变化
Button(this.failed ? '重试生成' : (this.completed ? '查看视频结果' : '正在做动画...'))
.backgroundColor((this.completed || this.failed) ? this.brandPurple : '#A9A0D8')
}
}
@State 的联动效果:
statusText变化 → 显示文本更新failed为true→ 文本变红色(#D94C3D),按钮显示"重试生成"completed为true→ 按钮显示"查看视频结果",背景色恢复为品牌紫色
一个状态变量可以同时影响多个组件的多个属性,这正是声明式 UI 的强大之处。
三、异步状态更新(定时器)
在实际应用中,状态往往不是一次性变化的,而是通过异步操作逐步更新。项目中的生成等待页面使用了 setInterval 来模拟进度推进:
typescript
private startWaitingTimer() {
this.timerId = setInterval(() => {
if (this.failed || this.completed) {
clearInterval(this.timerId);
return;
}
// 更新动画帧计数(用于气泡动画)
this.animationFrame = (this.animationFrame + 1) % 4;
// 更新进度
if (this.progress < 92) {
this.progress = Math.min(92, this.progress + 3);
} else {
this.progress = Math.min(97, this.progress + 1);
}
// 根据进度更新步骤索引
this.activeStep = Math.min(3, Math.floor(this.progress / 28));
}, 1200);
}
关键点:
- 每 1200ms 触发一次回调
- 修改
@State变量(progress、animationFrame、activeStep) - 每次修改触发 UI 重新渲染
failed或completed为true时停止定时器
Index 页面中也有类似的用法:
typescript
aboutToAppear() {
if (this.animationTimer < 0) {
this.animationTimer = setInterval(() => {
if (this.isPlaying && this.secondaryPage === SECONDARY_WORKS) {
this.animationFrame = (this.animationFrame + 1) % 8;
}
}, 650);
}
}
四、状态在 build() 中的条件渲染
@State 变量的一个核心用途是在 build() 方法中控制显示哪个页面或组件。
三元表达式控制页面切换
在 Index 组件的 CurrentPage @Builder 中,通过 secondaryPage 和 currentTab 控制页面显示:
typescript
@Builder
private CurrentPage() {
if (this.secondaryPage === SECONDARY_WORKS) {
this.WorksPage()
} else if (this.secondaryPage === SECONDARY_ANALYSIS) {
this.AnalysisPage()
} else if (this.currentTab === 0) {
this.HomePage()
} else if (this.currentTab === 1) {
this.CreationPage()
} else {
this.WorldPage()
}
}
build() {
Column() {
this.CurrentPage()
}
}
这种模式相当于一个简化版的路由系统:
secondaryPage === SECONDARY_NONE→ 显示首页/创作页/世界页(通过currentTab切换)secondaryPage === SECONDARY_WORKS→ 切换到作品详情二级页secondaryPage === SECONDARY_ANALYSIS→ 切换到分析二级页
当 openWorksSecondary() 被调用时:
typescript
private openWorksSecondary(): void {
this.secondaryPage = SECONDARY_WORKS; // 改变状态
this.noticeText = '';
this.refreshWorks();
}
@State secondaryPage 从 SECONDARY_NONE 变为 SECONDARY_WORKS,build() 中的条件判断结果改变,整个页面立即切换到作品页。
条件渲染的方式
ArkUI 中状态驱动的条件渲染有多种写法:
方式一:三元表达式(适合二选一)
typescript
// Index.ets - 在 build 中可用
this.secondaryPage === SECONDARY_WORKS ?
this.worksPage() : this.homePage()
方式二:if/else 语句(适合多分支)
typescript
// 在 @Builder 中使用 if/else
@Builder
private CurrentPage() {
if (this.secondaryPage === SECONDARY_WORKS) {
this.WorksPage()
} else if (...) {
// ...
}
}
注意:在
build()方法中,早期版本推荐使用三元表达式;在 @Builder 函数中可以使用if语句。
五、@Builder 中消费 @State
@Builder 是 ArkUI 中的自定义构建函数,它同样可以读取和消费 @State 变量。
状态驱动的 @Builder 参数
在 RecognitionWaitingPage 中,StepRow @Builder 根据 activeStep 状态动态改变样式:
typescript
@Builder
private StepRow(label: string, index: number) {
Row() {
Text((index + 1).toString())
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(this.activeStep >= index ? '#FFFFFF' : '#8A8FA4')
.backgroundColor(this.activeStep >= index ? this.mint : '#ECECF6')
.borderRadius(15)
Text(label)
.fontSize(13)
.fontColor(this.activeStep >= index ? this.ink : '#8A8FA4')
Text(this.activeStep > index ? '完成' : (this.activeStep === index ? '进行中' : '等待'))
.fontSize(11)
.fontColor(this.activeStep >= index ? this.mint : '#9AA0B5')
}
}
this.activeStep 是组件级的 @State 变量,@Builder 中可以直接访问。随着 activeStep 从 0 → 1 → 2 → 3 递增,每一步的图标颜色、文字颜色、状态标签都会联动变化。
状态驱动的动画气泡
另一个生动例子是 LoadingBubbles:
typescript
@Builder
private LoadingBubbles() {
Row() {
Circle()
.width(this.animationFrame === 0 ? 24 : 16)
.height(this.animationFrame === 0 ? 24 : 16)
.fill(this.sunshine)
.opacity(this.animationFrame === 0 ? 1 : 0.55)
Circle()
.width(this.animationFrame === 1 ? 24 : 16)
.height(this.animationFrame === 1 ? 24 : 16)
.fill(this.mint)
.opacity(this.animationFrame === 1 ? 1 : 0.55)
.margin({ left: 12 })
Circle()
.width(this.animationFrame === 2 ? 24 : 16)
.height(this.animationFrame === 2 ? 24 : 16)
.fill(this.brandPurple)
.opacity(this.animationFrame === 2 ? 1 : 0.55)
.margin({ left: 12 })
}
}
animationFrame 每 1200ms 在 0~3 之间循环切换,三个圆点依次变大变亮,形成波浪动画效果。纯状态驱动的无代码动画,无需 Animation API 也能实现生动的加载效果。
六、@State 与生命周期
@State 变量的初始化和清理与组件的生命周期紧密相关。
aboutToAppear:初始化状态
当组件即将挂载到页面时触发,适合在此处初始化 @State 变量或启动定时器:
typescript
aboutToAppear() {
// 注册断点系统
this.breakpointSystem.register(this.getUIContext());
// 注册碰一碰分享
this.registerKnockShare();
// 刷新作品列表
this.refreshWorks();
// 处理路由参数
const params = this.getUIContext().getRouter().getParams() as IndexParams;
if (params && params.tab === 'works') {
this.openWorkDetail(/*...*/);
}
// 启动定时器更新 animationFrame
if (this.animationTimer < 0) {
this.animationTimer = setInterval(() => {
if (this.isPlaying && this.secondaryPage === SECONDARY_WORKS) {
this.animationFrame = (this.animationFrame + 1) % 8;
}
}, 650);
}
}
RecognitionWaitingPage 中的初始化:
typescript
aboutToAppear() {
// 从路由参数读取并初始化 @State 变量
const params = this.getUIContext().getRouter().getParams() as WaitingParams;
if (params && params.source) {
this.source = params.source;
}
if (params && params.prompt) {
this.prompt = params.prompt;
}
// 启动定时器和异步任务
this.startWaitingTimer();
this.startGeneration();
}
aboutToDisappear:清理状态
当组件即将卸载时触发,必须在此处清理定时器等资源,防止内存泄漏:
typescript
aboutToDisappear(): void {
// 注销碰一碰分享
this.unregisterKnockShare();
// 清理定时器
if (this.animationTimer >= 0) {
clearInterval(this.animationTimer);
this.animationTimer = -1;
}
// 注销断点系统
this.breakpointSystem.unregister();
// 销毁语音识别服务
this.voiceRecognitionService.destroy();
}
RecognitionWaitingPage 中的清理:
typescript
aboutToDisappear() {
if (this.timerId >= 0) {
clearInterval(this.timerId);
this.timerId = -1;
}
}
重要原则 :凡是在 aboutToAppear 中开启的定时器、注册的监听器、创建的服务,都要在 aboutToDisappear 中对称地清理。
七、性能注意事项
虽然 @State 使用非常方便,但项目中也有一些值得注意的性能最佳实践:
1. @State 变量数量控制
@State 变量不宜过多。Index 组件中声明了约 20+ 个 @State 变量,对于复杂页面这是合理的。但过多的 @State 变量会增加框架的依赖追踪开销。
项目中可以看到合理的做法:将 UI 状态(currentTab、secondaryPage)与业务数据(chatMessages、savedWorks)分开管理。
2. 非 UI 状态使用普通成员变量
对于不需要驱动 UI 刷新的变量,使用普通 private 变量而非 @State:
typescript
// 非响应式------不需要驱动 UI
private readonly pagePadding: number = 18;
private readonly brandPurple: string = '#7657F3';
private readonly ink: string = '#1E2442';
private videoController: VideoController = new VideoController();
private animationTimer: number = -1;
这些成员变量要么是常量,要么是不需要 UI 绑定的控制器/定时器ID。用普通变量存储可以避免不必要的性能开销。
3. 定时器中更新 @State 的注意事项
在定时器回调中更新 @State 变量时,ArkUI 会自动将更新合并到下一个渲染帧,不会出现中间态的闪烁。但要注意避免在短时间内频繁触发大量 @State 更新,建议控制定时器间隔(项目中为 650ms~1200ms)。
八、状态管理全景图
本文只介绍了最基础的 @State。HarmonyOS 的状态管理体系是一个完整的层级结构:
| 装饰器 | 作用域 | 用途 |
|---|---|---|
@State |
组件内部 | 组件私有的响应式状态 |
@Link |
父子组件 | 父子组件间双向同步 |
@Prop |
父子组件 | 父 → 子单向数据传递 |
@StorageLink |
应用全局 | 跨组件/跨页面的全局状态(如项目中的 currentBreakpoint) |
@Consume/@Provide |
组件树 | 跨层级传递,避免逐层传递 |
项目中的 @StorageLink 示例:
typescript
@StorageLink('mainBreakpoint') currentBreakpoint: string = 'md';
@StorageLink 通过 AppStorage 实现全局状态共享,项目的断点系统通过它通知所有组件屏幕尺寸变化。
总结
本文通过"画伴梦工厂"的实际代码,深入讲解了 @State 的核心用法:
| 特性 | 说明 |
|---|---|
| 响应式绑定 | @State 变量变化自动驱动 UI 刷新 |
| 多类型支持 | number、boolean、string、数组、自定义接口 |
| 条件渲染 | 通过三元表达式或 if/else 控制页面切换 |
| @Builder 消费 | @Builder 中直接访问 @State,实现局部动态样式 |
| 异步更新 | setInterval 中修改 @State 实现进度和动画效果 |
| 生命周期 | aboutToAppear 初始化,aboutToDisappear 清理资源 |
| 性能意识 | 区分 @State 与普通成员变量,控制变量数量 |
下一篇: 第 1.4 篇将介绍 @Link/@Prop/@StorageLink------跨越组件边界的状态通信,让不同组件间的数据协同工作。
参考源码
本文所有代码均来自项目文件:
products/default/src/main/ets/pages/Index.ets--- 首页主组件,包含丰富的 @State 声明、条件渲染、定时器和生命周期管理products/default/src/main/ets/pages/RecognitionWaitingPage.ets--- 生成等待页,展示 @State 驱动进度条、文本、按钮和动画效果的完整实践