第4篇:状态管理——让界面动起来 鸿蒙中文编程
本文介绍了HarmonyOS应用开发中的状态管理概念,重点讲解了如何使用@State装饰器实现界面动态响应。文章首先通过自动售货机的例子形象说明状态管理的核心原理:状态变化驱动界面更新。然后详细讲解了@State装饰器的使用方法、事件处理机制以及不同类型的状态定义。最后通过计数器实例演示了如何实现点击按钮改变状态并自动更新界面显示。全文采用生活化比喻和代码示例相结合的方式,帮助开发者理解状态管理的
第4篇:状态管理——让界面动起来
本课目标:掌握@State装饰器和事件处理,让界面能响应用户操作
**作者:**中文编程倡导者—— 李金雨
预计课时:3课时(135分钟)
难度等级:⭐⭐⭐(进阶)
一、开篇引入
1.1 从"静态"到"动态"
前面的课程,我们做的界面都是"静态"的——就像一张照片,只能看,不能交互。
但真正的应用应该是"动态"的:
- 点击按钮,数字会增加
- 输入文字,界面会同步显示
- 切换开关,背景会变颜色
1.2 生活例子:自动售货机
想象一台自动售货机:
状态(State) = 机器当前的"情况"
- 库存:还剩3瓶可乐
- 投币:已投入5元
- 选择:用户选了可乐
事件(Event) = 用户做的"动作"
- 投入硬币 → 金额增加
- 按下按钮 → 选择商品
- 确认购买 → 出货、找零
界面更新 = 显示的变化
- 投币后,屏幕显示"已投5元"
- 选择后,对应商品灯亮起
- 购买后,商品掉下来
这就是状态管理的核心:状态变化 → 界面自动更新
1.3 本课目标
今天我们要学习:
- 什么是状态(State)
- 怎么用
@State创建状态 - 怎么处理事件(点击、输入等)
- 实战:计数器、点赞按钮、简易计算器
1.4 预期成果
完成本课后,你能做出这样的应用:
计数器: 点赞按钮: 简易计算器:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ │ │ │ │ 123+456 │
│ 当前计数 │ │ ❤️ 999 │ ├─────────────┤
│ │ │ │ │ 7 8 9 ÷ │
│ 15 │ │ [点赞按钮] │ │ 4 5 6 × │
│ │ │ │ │ 1 2 3 - │
│ [-] [+] │ └─────────────┘ │ 0 . = + │
│ │ │ │
└─────────────┘ └─────────────┘
二、概念讲解
2.1 什么是状态(State)?
定义
状态就是会变化的数据。
当状态变化时,使用这个状态的界面会自动更新。
生活例子:温度计
温度计
┌───┐
│ │
│ │
│███│ ← 红色液柱(状态)
│███│
│███│
└───┘
25°C ← 显示的数字(界面)
- 状态:温度计里的液柱高度
- 界面:显示的温度数字
- 变化:温度升高 → 液柱上升 → 数字变大
程序中的状态
@State 温度: number = 25 // 这是一个状态
// 当温度变化时
this.温度 = 30 // 界面会自动显示30
2.2 @State装饰器——标记状态
什么是装饰器?
装饰器就像给数据贴一个"特殊标签",告诉ArkTS:“这个数据很重要,它变化时要通知界面更新!”
@State 计数: number = 0
| 部分 | 含义 |
|---|---|
@State |
装饰器,标记这是状态 |
计数 |
状态的名字 |
number |
状态的类型 |
0 |
初始值 |
使用状态
@Entry
@Component
struct 计数器 {
@State 计数: number = 0 // 定义状态
build() {
Column() {
// 使用状态显示在界面上
Text(`当前计数: ${this.计数}`)
.fontSize(40)
Button("增加")
.onClick(() => {
// 修改状态
this.计数 = this.计数 + 1
})
}
}
}
关键点:
- 用
@State定义状态 - 用
this.状态名读取状态 - 给状态赋新值,界面自动更新
2.3 事件处理——响应用户操作
什么是事件?
事件就是用户做的动作,比如:
- 点击按钮
- 输入文字
- 滑动屏幕
- 长按图片
点击事件(onClick)
Button("点我")
.onClick(() => {
// 点击后要做的事情
console.log("按钮被点击了!")
this.计数 = this.计数 + 1
})
输入事件(onChange)
TextInput({ placeholder: "请输入" })
.onChange((输入的内容: string) => {
// 输入内容变化时要做的事情
this.用户名 = 输入的内容
})
滑动事件(onSwipe)
Image("xxx.jpg")
.onSwipe((事件: SwipeEvent) => {
if (事件.direction == SwipeDirection.Left) {
console.log("向左滑动了")
}
})
2.4 状态变化的流程
让我们看看点击按钮后发生了什么:
用户点击按钮
↓
触发 onClick 事件
↓
执行 this.计数 = this.计数 + 1
↓
状态发生变化
↓
ArkTS检测到状态变化
↓
自动重新渲染界面
↓
界面上显示新的数字
这就是响应式编程的魅力:数据驱动界面!
2.5 多种状态类型
数字状态
@State 计数: number = 0
@State 分数: number = 100
@State 价格: number = 99.9
文字状态
@State 用户名: string = ""
@State 提示信息: string = "请输入用户名"
@State 当前页面: string = "首页"
真假状态
@State 是否登录: boolean = false
@State 是否加载中: boolean = true
@State 是否夜间模式: boolean = false
数组状态
@State 待办列表: string[] = ["吃饭", "睡觉", "打豆豆"]
@State 购物车: object[] = []
三、动手实践
3.1 基础练习:计数器
做一个可以加减数字的计数器:
// 完整可运行代码,复制到 Index.ets 即可运行
@Entry
@Component
struct Index {
@State 计数: number = 0
build() {
Column({ space: 30 }) {
// 标题
Text("计数器")
.fontSize(35)
.fontWeight(FontWeight.Bold)
.fontColor("#333333")
// 显示当前计数
Text(`${this.计数}`)
.fontSize(80)
.fontWeight(FontWeight.Bold)
.fontColor(this.计数 >= 0 ? "#4CAF50" : "#F44336")
// 操作按钮
Row({ space: 30 }) {
// 减号按钮
Button("-", { type: ButtonType.Circle })
.width(70)
.height(70)
.fontSize(40)
.backgroundColor("#FF5722")
.onClick(() => {
this.计数 = this.计数 - 1
})
// 重置按钮
Button("重置", { type: ButtonType.Capsule })
.width(100)
.height(50)
.backgroundColor("#9E9E9E")
.onClick(() => {
this.计数 = 0
})
// 加号按钮
Button("+", { type: ButtonType.Circle })
.width(70)
.height(70)
.fontSize(40)
.backgroundColor("#4CAF50")
.onClick(() => {
this.计数 = this.计数 + 1
})
}
// 快捷操作
Row({ space: 15 }) {
Button("+10")
.onClick(() => {
this.计数 = this.计数 + 10
})
Button("-10")
.onClick(() => {
this.计数 = this.计数 - 10
})
Button("×2")
.onClick(() => {
this.计数 = this.计数 * 2
})
}
.margin({ top: 20 })
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
.justifyContent(FlexAlign.Center)
}
}
3.2 进阶练习:点赞按钮
做一个带动画效果的点赞按钮:
// 完整可运行代码,复制到 Index.ets 即可运行
@Entry
@Component
struct Index {
@State 点赞数: number = 999
@State 是否已点赞: boolean = false
@State 心形大小: number = 1
build() {
Column({ space: 20 }) {
// 显示点赞数
Row({ space: 5 }) {
Text(this.是否已点赞 ? "❤️" : "🤍")
.fontSize(40 + this.心形大小 * 10)
.onClick(() => {
this.切换点赞状态()
})
Text(`${this.点赞数}`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
.fontColor(this.是否已点赞 ? "#F44336" : "#666666")
}
// 提示文字
Text(this.是否已点赞 ? "已点赞" : "点击点赞")
.fontSize(16)
.fontColor("#999999")
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
.justifyContent(FlexAlign.Center)
}
切换点赞状态() {
if (this.是否已点赞) {
// 取消点赞
this.点赞数 = this.点赞数 - 1
this.是否已点赞 = false
} else {
// 点赞
this.点赞数 = this.点赞数 + 1
this.是否已点赞 = true
// 简单动画效果
this.心形大小 = 1.5
setTimeout(() => {
this.心形大小 = 1
}, 200)
}
}
}
3.3 进阶练习:输入框同步显示
实现输入框内容和界面实时同步:
// 完整可运行代码,复制到 Index.ets 即可运行
@Entry
@Component
struct Index {
@State 用户名: string = ""
@State 密码: string = ""
@State 年龄: string = ""
build() {
Column({ space: 20 }) {
Text("实时预览")
.fontSize(30)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
// 输入区域
Column({ space: 15 }) {
// 用户名输入
Column({ space: 5 }) {
TextInput({ placeholder: "请输入用户名", text: this.用户名 })
.width("90%")
.height(50)
.backgroundColor("#F5F5F5")
.borderRadius(8)
.onChange((value: string) => {
this.用户名 = value
})
Text(`当前输入:${this.用户名}`)
.fontSize(14)
.fontColor("#666666")
}
// 密码输入
Column({ space: 5 }) {
TextInput({ placeholder: "请输入密码", text: this.密码 })
.width("90%")
.height(50)
.backgroundColor("#F5F5F5")
.borderRadius(8)
.type(InputType.Password)
.onChange((value: string) => {
this.密码 = value
})
Text(`密码长度:${this.密码.length}位`)
.fontSize(14)
.fontColor("#666666")
}
// 年龄输入
Column({ space: 5 }) {
TextInput({ placeholder: "请输入年龄", text: this.年龄 })
.width("90%")
.height(50)
.backgroundColor("#F5F5F5")
.borderRadius(8)
.type(InputType.Number)
.onChange((value: string) => {
this.年龄 = value
})
// 根据年龄显示不同提示
if (this.年龄 != "") {
let 年龄数字: number = parseInt(this.年龄)
if (年龄数字 < 0) {
Text("年龄不能为负数!")
.fontSize(14)
.fontColor("#F44336")
} else if (年龄数字 < 18) {
Text("未成年用户")
.fontSize(14)
.fontColor("#FF9800")
} else if (年龄数字 > 100) {
Text("年龄过大,请检查")
.fontSize(14)
.fontColor("#F44336")
} else {
Text("年龄合法")
.fontSize(14)
.fontColor("#4CAF50")
}
}
}
}
// 信息汇总卡片
Column({ space: 10 }) {
Text("信息汇总")
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text(`用户名:${this.用户名 || "未填写"}`)
Text(`密码:${"*".repeat(this.密码.length) || "未填写"}`)
Text(`年龄:${this.年龄 || "未填写"}`)
}
.width("90%")
.padding(20)
.backgroundColor("#E3F2FD")
.borderRadius(12)
.margin({ top: 20 })
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#FFFFFF')
}
}
3.4 挑战练习:简易计算器
做一个功能完整的计算器:
// 完整可运行代码,复制到 Index.ets 即可运行
@Entry
@Component
struct Index {
@State 显示内容: string = "0"
@State 上一个数字: number = 0
@State 当前操作符: string = ""
@State 是否刚输入操作符: boolean = false
build() {
Column() {
// 显示区域
Column() {
Text(this.显示内容)
.fontSize(60)
.fontWeight(FontWeight.Medium)
.width('100%')
.textAlign(TextAlign.End)
.padding(20)
}
.width('100%')
.height(150)
.backgroundColor('#333333')
.justifyContent(FlexAlign.End)
// 按钮区域
Column({ space: 2 }) {
// 第一行:C、÷、×、删除
Row({ space: 2 }) {
this.功能按钮("C", "#FF5722", () => this.清空())
this.功能按钮("÷", "#FF9800", () => this.设置操作符("÷"))
this.功能按钮("×", "#FF9800", () => this.设置操作符("×"))
this.功能按钮("⌫", "#FF9800", () => this.删除())
}
.height(80)
// 第二行:7、8、9、-
Row({ space: 2 }) {
this.数字按钮("7")
this.数字按钮("8")
this.数字按钮("9")
this.功能按钮("-", "#FF9800", () => this.设置操作符("-"))
}
.height(80)
// 第三行:4、5、6、+
Row({ space: 2 }) {
this.数字按钮("4")
this.数字按钮("5")
this.数字按钮("6")
this.功能按钮("+", "#FF9800", () => this.设置操作符("+"))
}
.height(80)
// 第四行:1、2、3、=
Row({ space: 2 }) {
this.数字按钮("1")
this.数字按钮("2")
this.数字按钮("3")
this.功能按钮("=", "#4CAF50", () => this.计算结果())
.height(162) // 跨两行的高度
}
.height(80)
// 第五行:0、.
Row({ space: 2 }) {
this.数字按钮("0")
.width('50%') // 0按钮占两个位置
this.数字按钮(".")
this.功能按钮("", "#333333", () => {})
.visibility(Visibility.Hidden) // 占位
}
.height(80)
}
.width('100%')
.layoutWeight(1)
.backgroundColor('#CCCCCC')
.padding(2)
}
.width('100%')
.height('100%')
}
// 数字按钮
@Builder
数字按钮(数字: string) {
Button(数字)
.width('25%')
.height('100%')
.fontSize(30)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FFFFFF')
.fontColor('#333333')
.onClick(() => {
this.输入数字(数字)
})
}
// 功能按钮
@Builder
功能按钮(文字: string, 颜色: string, 点击动作: () => void) {
Button(文字)
.width('25%')
.height('100%')
.fontSize(30)
.fontWeight(FontWeight.Medium)
.backgroundColor(颜色)
.fontColor('#FFFFFF')
.onClick(点击动作)
}
// 输入数字
输入数字(数字: string) {
if (this.显示内容 == "0" || this.是否刚输入操作符) {
this.显示内容 = 数字
this.是否刚输入操作符 = false
} else {
this.显示内容 = this.显示内容 + 数字
}
}
// 设置操作符
设置操作符(操作符: string) {
this.上一个数字 = parseFloat(this.显示内容)
this.当前操作符 = 操作符
this.是否刚输入操作符 = true
}
// 计算结果
计算结果() {
let 当前数字: number = parseFloat(this.显示内容)
let 结果: number = 0
switch (this.当前操作符) {
case "+":
结果 = this.上一个数字 + 当前数字
break
case "-":
结果 = this.上一个数字 - 当前数字
break
case "×":
结果 = this.上一个数字 * 当前数字
break
case "÷":
if (当前数字 == 0) {
this.显示内容 = "错误"
return
}
结果 = this.上一个数字 / 当前数字
break
default:
return
}
this.显示内容 = 结果.toString()
this.当前操作符 = ""
}
// 清空
清空() {
this.显示内容 = "0"
this.上一个数字 = 0
this.当前操作符 = ""
this.是否刚输入操作符 = false
}
// 删除
删除() {
if (this.显示内容.length > 1) {
this.显示内容 = this.显示内容.substring(0, this.显示内容.length - 1)
} else {
this.显示内容 = "0"
}
}
}
四、知识总结
4.1 核心概念回顾
- 状态(State):会变化的数据,用
@State标记 - 事件(Event):用户的操作,如点击、输入
- 响应式更新:状态变化 → 界面自动更新
4.2 状态管理流程
定义状态
↓
在界面中使用状态
↓
用户触发事件
↓
修改状态值
↓
界面自动刷新
4.3 关键代码速查
// 定义状态
@State 状态名: 类型 = 初始值
// 读取状态
this.状态名
// 修改状态
this.状态名 = 新值
// 点击事件
.onClick(() => {
// 点击后做的事
})
// 输入事件
.onChange((新值: 类型) => {
this.状态名 = 新值
})
4.4 常见错误提醒
| 错误现象 | 原因 | 解决方法 |
|---|---|---|
| 界面不更新 | 忘记加@State | 给变量加上@State装饰器 |
| 状态无法修改 | 用了const | 改用let或@State |
| 点击没反应 | onClick写错位置 | 确保onClick跟在组件后面 |
| 状态值不对 | 类型不匹配 | 检查类型定义和赋值是否一致 |
| 数组不更新 | 直接修改数组元素 | 使用push/pop等方法或重新赋值 |
五、课后作业
5.1 巩固练习(必做)
练习1:待办事项列表
做一个简单的待办事项应用:
- 输入框添加新事项
- 点击事项标记完成(文字加删除线)
- 显示已完成/未完成数量
练习2:开关控制器
做一个控制面板:
- 多个开关(WiFi、蓝牙、飞行模式)
- 开关状态改变时背景色变化
- 显示当前开启的功能数量
练习3:投票系统
做一个简单的投票应用:
- 多个选项
- 点击选项投票
- 实时显示每个选项的票数
5.2 创意编程(选做)
创意1:计时器
- 开始/暂停/重置按钮
- 显示经过的时间
- 记录多圈时间
创意2:猜数字游戏
- 系统随机生成一个数字
- 用户输入猜测
- 提示"太大"或"太小"
- 记录猜测次数
创意3:简易画板
- 选择画笔颜色
- 选择画笔粗细
- 清空画布
- 保存作品
5.3 下篇预习
下一篇,我们将学习列表渲染,展示大量数据。预习问题:
- 怎么显示一个班级50个学生的信息?
- 怎么让列表可以滚动?
- 怎么点击列表项进行操作?
附录:更多状态管理技巧
技巧1:状态联动
@State 单价: number = 10
@State 数量: number = 1
// 计算属性(自动更新)
get 总价(): number {
return this.单价 * this.数量
}
技巧2:对象状态
interface 用户信息 {
姓名: string
年龄: number
}
@State 用户: 用户信息 = { 姓名: "张三", 年龄: 20 }
// 修改对象属性
this.用户.姓名 = "李四" // 这样不会触发更新!
// 正确做法:重新赋值整个对象
this.用户 = { 姓名: "李四", 年龄: this.用户.年龄 }
技巧3:数组状态操作
@State 列表: string[] = ["a", "b", "c"]
// 添加元素
this.列表.push("d") // 不会触发更新!
this.列表 = [...this.列表, "d"] // 正确做法
// 删除元素
this.列表.splice(1, 1) // 不会触发更新!
this.列表 = this.列表.filter((_, i) => i != 1) // 正确做法
// 修改元素
this.列表[0] = "A" // 不会触发更新!
this.列表 = this.列表.map((item, i) => i == 0 ? "A" : item) // 正确做法
恭喜你完成了第4篇的学习! 🎉
现在你已经掌握了状态管理的核心概念,可以做出能交互的应用了。记住:数据驱动界面,只要管理好状态,界面就会自动更新!
下节课,我们将学习如何处理大量数据的展示!
更多推荐


所有评论(0)