鸿蒙开发之:状态管理与数据绑定
在鸿蒙应用开发中,UI是随着数据的变化而变化的。当数据发生变化时,UI需要重新渲染以反映最新的数据。状态管理就是用来管理这些数据的变化,并确保UI能够及时更新。ArkTS提供了多种状态管理装饰器,每种装饰器都有其特定的使用场景。理解这些装饰器的区别和用法,是构建复杂应用的基础。typescript// 商品模型id: number;// 购物车项模型// 计算总价@State:组件内部状态,变化触
本文字数:约3500字 | 预计阅读时间:15分钟
前置知识:建议先学习本系列前三篇,特别是ArkTS语言基础和ArkUI组件布局
实战价值:掌握状态管理,才能构建出数据驱动、响应式的鸿蒙应用
系列导航:本文是《鸿蒙开发系列》第4篇,下篇将讲解网络请求与数据处理
一、状态管理概述:为什么需要状态管理?
在鸿蒙应用开发中,UI是随着数据的变化而变化的。当数据发生变化时,UI需要重新渲染以反映最新的数据。状态管理就是用来管理这些数据的变化,并确保UI能够及时更新。
ArkTS提供了多种状态管理装饰器,每种装饰器都有其特定的使用场景。理解这些装饰器的区别和用法,是构建复杂应用的基础。
二、@State:组件内部的状态
@State装饰的变量是组件内部的状态数据,当状态数据发生变化时,会触发UI重新渲染。
2.1 基本用法
typescript
@Entry
@Component
struct StateDemo {
// 使用@State装饰器,表示count是组件的状态
@State count: number = 0;
build() {
Column({ space: 20 }) {
Text(`当前计数:${this.count}`)
.fontSize(30)
Button('增加')
.onClick(() => {
this.count++; // 修改状态,UI会自动更新
})
Button('减少')
.onClick(() => {
this.count--;
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
2.2 复杂对象的状态管理
typescript
// 定义对象类型
class User {
name: string = '';
age: number = 0;
}
@Entry
@Component
struct UserInfo {
// 对象类型的状态
@State user: User = new User();
build() {
Column({ space: 20 }) {
Text(`姓名:${this.user.name}`)
Text(`年龄:${this.user.age}`)
Button('修改用户信息')
.onClick(() => {
// 直接修改对象的属性,UI不会更新
// this.user.name = '张三';
// this.user.age = 20;
// 正确做法:创建一个新对象并整体赋值
this.user = {
name: '张三',
age: 20
} as User;
})
}
.padding(20)
}
}
注意:当@State装饰的对象属性发生变化时,需要整体赋值才能触发UI更新。如果只是修改对象的属性,UI不会更新。
三、@Prop:父子组件单向同步
@Prop装饰的变量是从父组件传递到子组件的,并且在子组件内部的变化不会同步回父组件,即单向同步。
3.1 基本用法
typescript
// 子组件
@Component
struct ChildComponent {
// 使用@Prop装饰器,表示title是从父组件传递过来的
@Prop title: string;
build() {
Column() {
Text(this.title)
.fontSize(20)
Button('修改标题')
.onClick(() => {
this.title = '子组件修改后的标题'; // 仅子组件内变化,不会同步回父组件
})
}
.padding(20)
.border({ width: 1, color: Color.Gray })
}
}
// 父组件
@Entry
@Component
struct ParentComponent {
@State parentTitle: string = '父组件标题';
build() {
Column({ space: 20 }) {
Text(`父组件标题:${this.parentTitle}`)
.fontSize(20)
// 将父组件的parentTitle传递给子组件的title
ChildComponent({ title: this.parentTitle })
Button('父组件修改标题')
.onClick(() => {
this.parentTitle = '父组件修改了标题'; // 父组件修改,会同步到子组件
})
}
.width('100%')
.height('100%')
.padding(20)
}
}
四、@Link:父子组件双向同步
@Link装饰的变量也是从父组件传递到子组件的,但是子组件内部的变化会同步回父组件,即双向同步。
4.1 基本用法
typescript
// 子组件
@Component
struct ChildComponent {
// 使用@Link装饰器,表示title与父组件双向同步
@Link title: string;
build() {
Column() {
Text(this.title)
.fontSize(20)
Button('子组件修改标题')
.onClick(() => {
this.title = '子组件修改后的标题'; // 子组件修改,会同步回父组件
})
}
.padding(20)
.border({ width: 1, color: Color.Gray })
}
}
// 父组件
@Entry
@Component
struct ParentComponent {
@State parentTitle: string = '父组件标题';
build() {
Column({ space: 20 }) {
Text(`父组件标题:${this.parentTitle}`)
.fontSize(20)
// 使用$符号创建引用,实现双向同步
ChildComponent({ title: $parentTitle })
Button('父组件修改标题')
.onClick(() => {
this.parentTitle = '父组件修改了标题'; // 父组件修改,会同步到子组件
})
}
.width('100%')
.height('100%')
.padding(20)
}
}
注意:在父组件中传递@Link变量时,需要使用$符号来创建引用。
五、@Watch:状态变化的监听器
@Watch装饰器用于监听状态变量的变化,当状态变量发生变化时,会触发指定的回调函数。
5.1 基本用法
typescript
@Entry
@Component
struct WatchDemo {
@State count: number = 0;
// 使用@Watch装饰器监听count的变化
@Watch('onCountChange') @State watchedCount: number = 0;
// 监听回调函数
onCountChange(): void {
console.log(`count发生变化,新值为:${this.count}`);
// 可以在这里执行一些副作用,比如发送网络请求、保存数据等
}
build() {
Column({ space: 20 }) {
Text(`当前计数:${this.count}`)
.fontSize(30)
Button('增加')
.onClick(() => {
this.count++;
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
注意:@Watch装饰器需要放在状态装饰器(如@State)之前,并且监听的是同一个组件内的状态变量。
六、@Provide和@Consume:跨组件层级的数据同步
@Provide和@Consume装饰器用于跨组件层级的数据同步,不需要通过中间组件逐层传递。
6.1 基本用法
typescript
// 孙子组件
@Component
struct GrandChildComponent {
// 使用@Consume装饰器,消费由祖先组件提供的值
@Consume message: string;
build() {
Column() {
Text(`孙子组件收到的消息:${this.message}`)
.fontSize(20)
Button('孙子组件修改消息')
.onClick(() => {
this.message = '孙子组件修改了消息'; // 修改会同步到所有消费该数据的组件
})
}
.padding(20)
.border({ width: 1, color: Color.Green })
}
}
// 子组件
@Component
struct ChildComponent {
build() {
Column() {
Text('这是子组件')
.fontSize(20)
GrandChildComponent()
}
.padding(20)
.border({ width: 1, color: Color.Blue })
}
}
// 父组件
@Entry
@Component
struct ParentComponent {
// 使用@Provide装饰器,提供数据给后代组件
@Provide message: string = '初始消息';
build() {
Column({ space: 20 }) {
Text(`父组件消息:${this.message}`)
.fontSize(20)
ChildComponent()
Button('父组件修改消息')
.onClick(() => {
this.message = '父组件修改了消息';
})
}
.width('100%')
.height('100%')
.padding(20)
}
}
使用@Provide和@Consume,可以在任意层级的组件之间进行数据同步,而不需要通过props逐层传递。
七、状态管理实战:购物车应用
下面我们通过一个购物车应用来综合运用上述状态管理装饰器。
7.1 定义数据模型
typescript
// 商品模型
class Product {
id: number;
name: string;
price: number;
image: string;
constructor(id: number, name: string, price: number, image: string) {
this.id = id;
this.name = name;
this.price = price;
this.image = image;
}
}
// 购物车项模型
class CartItem {
product: Product;
quantity: number;
constructor(product: Product, quantity: number = 1) {
this.product = product;
this.quantity = quantity;
}
// 计算总价
get totalPrice(): number {
return this.product.price * this.quantity;
}
}
7.2 商品列表组件
typescript
@Component
struct ProductItem {
// 商品数据,从父组件传递,单向同步
@Prop product: Product;
// 添加购物车的回调函数
onAddToCart: (product: Product) => void;
build() {
Row({ space: 12 }) {
Image(this.product.image)
.width(80)
.height(80)
.objectFit(ImageFit.Cover)
Column({ space: 8 }) {
Text(this.product.name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text(`¥${this.product.price}`)
.fontSize(18)
.fontColor('#FF6B00')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Button('加入购物车')
.width(100)
.height(36)
.fontSize(14)
.onClick(() => {
this.onAddToCart(this.product);
})
}
.width('100%')
.padding(12)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 4, color: '#10000000' })
}
}
7.3 购物车组件
typescript
@Component
struct CartItemComponent {
// 购物车项数据,双向同步
@Link cartItem: CartItem;
// 删除购物车项的回调
onDeleteItem: (item: CartItem) => void;
build() {
Row({ space: 12 }) {
Image(this.cartItem.product.image)
.width(60)
.height(60)
.objectFit(ImageFit.Cover)
Column({ space: 4 }) {
Text(this.cartItem.product.name)
.fontSize(16)
Text(`单价:¥${this.cartItem.product.price}`)
.fontSize(14)
.fontColor('#666666')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Row({ space: 8 }) {
Button('-')
.width(30)
.height(30)
.fontSize(16)
.onClick(() => {
if (this.cartItem.quantity > 1) {
this.cartItem.quantity--;
}
})
Text(`${this.cartItem.quantity}`)
.width(40)
.textAlign(TextAlign.Center)
Button('+')
.width(30)
.height(30)
.fontSize(16)
.onClick(() => {
this.cartItem.quantity++;
})
}
Text(`¥${this.cartItem.totalPrice}`)
.width(80)
.fontSize(16)
.fontColor('#FF6B00')
.textAlign(TextAlign.End)
Button('删除')
.width(60)
.height(30)
.fontSize(12)
.backgroundColor('#FF3B30')
.onClick(() => {
this.onDeleteItem(this.cartItem);
})
}
.width('100%')
.padding(12)
.backgroundColor(Color.White)
.borderRadius(8)
}
}
7.4 主页面
typescript
@Entry
@Component
struct ShoppingCartPage {
// 商品列表状态
@State products: Product[] = [
new Product(1, '华为Mate 60', 6999, 'mate60.jpg'),
new Product(2, '华为Watch 4', 2699, 'watch4.jpg'),
new Product(3, '华为平板', 3299, 'tablet.jpg'),
];
// 购物车状态
@State cartItems: CartItem[] = [];
// 计算总价
get totalPrice(): number {
return this.cartItems.reduce((sum, item) => sum + item.totalPrice, 0);
}
// 添加商品到购物车
addToCart(product: Product) {
const existingItem = this.cartItems.find(item => item.product.id === product.id);
if (existingItem) {
existingItem.quantity++;
} else {
this.cartItems.push(new CartItem(product, 1));
}
}
// 从购物车删除商品
deleteFromCart(itemToDelete: CartItem) {
const index = this.cartItems.findIndex(item => item === itemToDelete);
if (index !== -1) {
this.cartItems.splice(index, 1);
}
}
build() {
Column({ space: 20 }) {
// 商品列表
Text('商品列表')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Start)
Column({ space: 12 }) {
ForEach(this.products, (product: Product) => {
ProductItem({
product: product,
onAddToCart: (product: Product) => {
this.addToCart(product);
}
})
})
}
.width('100%')
// 购物车
Text(`购物车 (${this.cartItems.length})`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Start)
if (this.cartItems.length === 0) {
Text('购物车空空如也')
.fontSize(16)
.fontColor('#999999')
.width('100%')
.textAlign(TextAlign.Center)
.padding(40)
} else {
Column({ space: 12 }) {
ForEach(this.cartItems, (item: CartItem) => {
CartItemComponent({
cartItem: $item, // 使用$创建引用,实现双向同步
onDeleteItem: (item: CartItem) => {
this.deleteFromCart(item);
}
})
})
// 总计
Row() {
Text('总计:')
.fontSize(18)
Blank()
Text(`¥${this.totalPrice}`)
.fontSize(24)
.fontColor('#FF6B00')
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding({ top: 20, bottom: 20 })
Button('去结算')
.width('100%')
.height(50)
.fontSize(18)
.backgroundColor('#007DFF')
.fontColor(Color.White)
}
.width('100%')
}
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#F5F5F5')
}
}
八、状态管理最佳实践
8.1 状态提升
将多个组件需要共享的状态提升到最近的共同父组件中管理。
8.2 合理选择装饰器
-
组件内部状态:@State
-
父子组件单向同步:@Prop
-
父子组件双向同步:@Link
-
跨组件层级同步:@Provide和@Consume
-
监听状态变化:@Watch
8.3 避免不必要的渲染
-
使用@State时,对于对象类型,避免直接修改属性,应该整体赋值
-
使用@Prop时,如果父组件频繁更新但子组件不需要重新渲染,可以考虑使用@Link或@Consume
8.4 状态分离
将状态逻辑与UI分离,可以使用自定义类或函数来管理复杂的状态逻辑。
九、常见问题与解决方案
问题1:@State装饰的对象属性变化,UI不更新
原因:直接修改了对象的属性,而不是整体赋值。
解决:创建一个新对象并整体赋值。
问题2:@Prop和@Link的区别
-
@Prop是单向同步:父组件到子组件
-
@Link是双向同步:父组件和子组件相互同步
问题3:@Watch回调函数中修改状态导致无限循环
解决:确保@Watch回调函数中修改的状态不会再次触发同一个@Watch回调。
十、总结与下期预告
10.1 本文要点回顾
-
@State:组件内部状态,变化触发UI更新
-
@Prop:父子组件单向同步
-
@Link:父子组件双向同步
-
@Watch:监听状态变化
-
@Provide和@Consume:跨组件层级同步
-
实战:购物车应用的综合运用
10.2 下期预告:《鸿蒙开发之:网络请求与数据处理》
下篇文章将深入讲解:
-
使用HTTP模块进行网络请求
-
处理JSON数据
-
异步编程:Promise和async/await
-
数据缓存策略
-
实战:构建一个新闻客户端
动手挑战
任务1:实现一个计数器应用
要求:
-
包含两个计数器A和B
-
计数器A每增加10,计数器B自动增加1(使用@Watch)
-
提供重置按钮,重置两个计数器
任务2:实现一个任务管理应用
要求:
-
可以添加、删除、标记任务完成
-
显示未完成任务数量和总任务数量
-
使用@Provide和@Consume实现状态共享
任务3:优化购物车应用
要求:
-
增加商品库存概念,购买数量不能超过库存
-
实现购物车本地持久化(可以使用LocalStorage)
-
增加优惠券功能,结算时自动扣减
将你的代码分享到评论区,我会挑选优秀实现进行详细点评!
常见问题解答
Q:@State和@Link可以一起使用吗?
A:可以。@State用于管理组件内部状态,@Link用于与子组件双向同步。在父组件中使用@State,然后通过$符号传递给子组件的@Link。
Q:@Watch可以监听多个状态变量吗?
A:可以。每个状态变量都可以有自己的@Watch装饰器,分别指定不同的回调函数。
Q:@Provide和@Consume与@Link有什么区别?
A:@Provide和@Consume用于跨任意组件层级的数据同步,而@Link只能在父子组件之间使用。@Provide和@Consume更适合全局状态管理。
Q:状态管理会导致性能问题吗?
A:不合理的使用状态管理可能会导致不必要的UI渲染,从而影响性能。建议遵循最佳实践,如状态提升、合理选择装饰器等。
PS:现在HarmonyOS应用开发者认证正在做活动,初级和高级都可以免费学习及考试,赶快加入班级学习啦:【注意,考试只能从此唯一链接进入】
https://developer.huawei.com/consumer/cn/training/classDetail/33f85412dc974764831435dc1c03427c?type=1?ha_source=hmosclass&ha_sourceld=89000248
版权声明:本文为《鸿蒙开发系列》第4篇,原创文章,转载请注明出处。
标签:#HarmonyOS #鸿蒙开发 #状态管理 #数据绑定 #ArkUI #华为开发者
更多推荐


所有评论(0)