在 HarmonyOS 的 ArkUI 状态管理(V1)中,@Prop、@ObjectLink 与 @Observed 的组合是处理复杂数据流、实现高性能 UI 渲染的核心。要深度联动这三个装饰器,首先需要明确它们各自的职责与边界,然后掌握在不同业务场景下的最佳实践。
一、 核心概念与联动机制
- @Observed(类装饰器):它的核心作用是赋予类实例“可被深度观察”的能力。默认情况下,状态管理装饰器(如 @State、@Prop)只能观察到对象第一层属性的变化。当一个类被 @Observed 装饰后,框架会为其创建不透明的代理对象,从而能够拦截并观察到嵌套对象(第二层及以下)的属性变化。
- @ObjectLink(变量装饰器):它是 @Observed 的“搭档”,专门用于在子组件中接收 @Observed 装饰的类实例,并与父组件建立双向数据同步。它相当于一个指向父组件数据源的指针,修改其属性会直接修改父组件的数据,且自身不允许被整体重新赋值。
- @Prop(变量装饰器):用于在父子组件间建立单向数据同步。子组件可以修改 @Prop 变量,但修改不会同步回父组件。当父组件数据源更新时,子组件的 @Prop 会被覆盖。
1、 深度联动实例:@Observed + @ObjectLink(双向嵌套同步)
业务场景:父组件持有一个复杂的嵌套对象,子组件需要直接修改该对象深层的属性,并且修改结果需要实时同步回父组件。
// 1. 必须使用 @Observed 装饰嵌套的类,赋予其被深度观察的能力
@Observed
class InnerInfo {
public detail: string;
constructor(detail: string) {
this.detail = detail;
}
}
@Observed
class OuterModel {
public name: string;
public inner: InnerInfo;
constructor(name: string, inner: InnerInfo) {
this.name = name;
this.inner = inner;
}
}
// 2. 子组件使用 @ObjectLink 接收,建立双向同步
@Component
struct ChildComponent {
// 接收父组件传入的 @Observed 类实例
@ObjectLink model: OuterModel;
build() {
Column({ space: 10 }) {
Text(`Name: ${this.model.name}`)
// 能够观察到第二层嵌套对象 inner 的属性变化
Text(`Detail: ${this.model.inner.detail}`)
Button('修改深层属性')
.onClick(() => {
// 直接修改深层属性,父组件也会同步更新
this.model.inner.detail = 'Updated by Child';
})
}
}
}
// 3. 父组件持有状态并传递给子组件
@Entry
@Component
struct ParentComponent {
@State myModel: OuterModel = new OuterModel('Parent', new InnerInfo('Initial Detail'));
build() {
Column({ space: 20 }) {
Text(`Parent Detail: ${this.myModel.inner.detail}`)
// 传递引用,不会发生深拷贝
ChildComponent({ model: this.myModel })
}
}
}
2、 性能对比实例:@Prop 的深拷贝开销 vs @ObjectLink 的零拷贝
业务场景:子组件仅用于展示列表项数据,不需要修改原始数据源。
@Observed
class Product {
public title: string;
constructor(title: string) {
this.title = title;
}
}
// 【反例】使用 @Prop 传递复杂对象(不推荐)
@Component
struct PropChild {
// @Prop 会对 Product 进行深拷贝,如果列表有100项,就会触发100次深拷贝,导致严重卡顿
@Prop product: Product;
build() {
Text(this.product.title)
}
}
// 【正例】使用 @ObjectLink 传递复杂对象(推荐)
@Component
struct ObjectLinkChild {
// 仅传递内存引用,无深拷贝开销,性能极佳
@ObjectLink product: Product;
build() {
Text(this.product.title)
}
}
@Entry
@Component
struct ListParent {
@State products: Product[] = [new Product('Phone'), new Product('Tablet')];
build() {
List() {
ForEach(this.products, (item: Product) => {
ListItem() {
// 优先使用 ObjectLinkChild
ObjectLinkChild({ product: item })
}
})
}
}
}
3、 单向同步实例:@Prop + @Observed(本地状态副本)
业务场景:父组件下发一个嵌套对象,子组件需要在本地对其进行修改(如展开/折叠状态、草稿编辑),但不希望影响父组件的原始数据。
@Observed
class Draft {
public content: string;
constructor(content: string) {
this.content = content;
}
}
@Component
struct DraftEditor {
// @Prop 会深拷贝一份 Draft 对象,子组件拥有独立副本
@Prop draft: Draft;
build() {
Column() {
Text(`Local Content: ${this.draft.content}`)
Button('本地修改')
.onClick(() => {
// 修改仅停留在当前子组件,不会同步回父组件
this.draft.content += ' (Edited Locally)';
})
}
}
}
@Entry
@Component
struct EditorParent {
@State originalDraft: Draft = new Draft('Original Text');
build() {
Column({ space: 20 }) {
Text(`Parent Content: ${this.originalDraft.content}`)
Button('父组件更新')
.onClick(() => {
// 父组件更新时,会覆盖子组件 @Prop 本地的所有修改
this.originalDraft.content = 'Reset by Parent';
})
DraftEditor({ draft: this.originalDraft })
}
}
}
二、 深度联动:@Prop 与 @Observed 的结合
虽然 @ObjectLink 是 @Observed 的最佳搭档,但 @Prop 同样可以与 @Observed 联动,实现嵌套对象的单向同步。
- 观察能力:当 @Prop 装饰的变量类型是被 @Observed 装饰的 class 时,它可以观察到该 class 第一层属性的变化。如果嵌套的属性也是 class,同样需要被 @Observed 装饰才能被观察到。
- 单向同步特性:与 @ObjectLink 不同,@Prop 会在本地拷贝一份数据源。子组件对 @Prop 嵌套对象属性的修改是允许的,但不会同步给父组件。如果父组件的数据源发生变化,子组件本地的修改将被覆盖。
业务场景:父组件下发一个嵌套对象,子组件需要在本地修改该对象的深层属性(如展开/折叠状态、草稿编辑),但不希望影响父组件的原始数据。
// 1. 嵌套类必须被 @Observed 装饰,否则深层属性变化无法被 @Prop 观察到
@Observed
class InnerInfo {
public detail: string;
constructor(detail: string) {
this.detail = detail;
}
}
@Observed
class OuterModel {
public name: string;
public inner: InnerInfo;
constructor(name: string, inner: InnerInfo) {
this.name = name;
this.inner = inner;
}
}
// 2. 子组件使用 @Prop 接收,建立单向同步
@Component
struct ChildComponent {
// @Prop 会深拷贝 OuterModel 及其嵌套对象,子组件拥有独立副本
@Prop model: OuterModel;
build() {
Column({ space: 10 }) {
Text(`Local Name: ${this.model.name}`)
Text(`Local Detail: ${this.model.inner.detail}`)
Button('本地修改深层属性')
.onClick(() => {
// 修改仅停留在当前子组件,不会同步回父组件
this.model.inner.detail = 'Edited by Child';
})
}
}
}
// 3. 父组件持有状态并传递给子组件
@Entry
@Component
struct ParentComponent {
@State myModel: OuterModel = new OuterModel('Parent', new InnerInfo('Initial Detail'));
build() {
Column({ space: 20 }) {
Text(`Parent Detail: ${this.myModel.inner.detail}`)
Button('父组件重置数据')
.onClick(() => {
// 父组件更新时,会覆盖子组件 @Prop 本地的所有修改
this.myModel.inner.detail = 'Reset by Parent';
})
ChildComponent({ model: this.myModel })
}
}
}
三、 性能避坑:@Prop 与 @ObjectLink 的抉择
在实际开发中,选择 @Prop 还是 @ObjectLink 对应用的性能有显著影响。
- @Prop 的深拷贝开销:当使用 @Prop 传递复杂对象(特别是被 @Observed 装饰的类)时,框架会执行深拷贝(Deep Copy)。如果嵌套层级深或数据量大,这会显著增加组件的创建时间并带来性能开销。
- 最佳实践:
- 子组件无需修改状态时:如果子组件仅仅是展示数据,不需要在本地修改该状态变量,强烈建议优先使用 @ObjectLink。因为它不会深拷贝数据,仅传递引用,性能远优于 @Prop。
- 子组件需要本地修改状态时:如果子组件需要修改传入的数据,且不希望影响父组件,此时才使用 @Prop。但需注意,在组件复用场景下,建议 @Prop 深度嵌套的数据不要超过 5 层,否则深拷贝和垃圾回收(GC)会引起严重的性能问题。若超过 5 层,应考虑重构数据结构或使用 @ObjectLink。
业务场景:在列表中渲染大量复杂数据项(如朋友圈动态、商品列表)。
@Observed
class FriendMoment {
public id: string;
public userName: string;
public text: string;
constructor(id: string, userName: string, text: string) {
this.id = id;
this.userName = userName;
this.text = text;
}
}
// 【反例】使用 @Prop 传递复杂对象(性能杀手)
@Component
struct MomentPropItem {
// 每次组件创建时,都会对 FriendMoment 进行深拷贝
// 如果列表有 100 条数据,就会触发 100 次深拷贝,导致列表滑动严重掉帧
@Prop moment: FriendMoment;
build() {
Text(`${this.moment.userName}: ${this.moment.text}`)
}
}
// 【正例】使用 @ObjectLink 传递复杂对象(高性能)
@Component
struct MomentObjectLinkItem {
// 仅传递内存引用(指针),无深拷贝开销
// 父子组件共享同一份数据,创建时间和内存消耗极低
@ObjectLink moment: FriendMoment;
build() {
Text(`${this.moment.userName}: ${this.moment.text}`)
}
}
@Entry
@Component
struct MomentListPage {
@State moments: FriendMoment[] = [
new FriendMoment('1', 'Alice', 'Hello HarmonyOS'),
new FriendMoment('2', 'Bob', 'ArkUI is great')
];
build() {
List() {
ForEach(this.moments, (item: FriendMoment) => {
ListItem() {
// 强烈建议使用正例写法
MomentObjectLinkItem({ moment: item })
}
}, (item: FriendMoment) => item.id) // 必须提供唯一 key
}
}
}
四、 联动实战指南
1、 对象数组与 ForEach 的深度联动
在实际开发中,最常见的复杂数据结构是“对象数组”(如商品列表、聊天记录)。如果直接传递数组项,框架默认只能观察到数组项的整体替换,无法观察到数组项内部属性的变化。
最佳实践:将数组项定义为 @Observed 类,在 ForEach 中将其作为 @ObjectLink 传递给子组件。
@Observed
class CartItem {
public id: number;
public count: number;
constructor(id: number, count: number) {
this.id = id;
this.count = count;
}
}
@Component
struct CartItemComponent {
// 接收数组中的具体项
@ObjectLink item: CartItem;
build() {
Row() {
Text(`商品ID: ${this.item.id}`)
Text(`数量: ${this.item.count}`)
Button('+').onClick(() => {
// 直接修改深层属性,UI 会精准刷新,且父组件的数组也会同步更新
this.item.count++;
})
}
}
}
@Entry
@Component
struct CartPage {
@State cartList: CartItem[] = [
new CartItem(1001, 1),
new CartItem(1002, 2)
];
build() {
List() {
ForEach(this.cartList, (item: CartItem) => {
ListItem() {
CartItemComponent({ item: item })
}
}, (item: CartItem) => item.id.toString()) // 必须提供唯一 key
}
}
}
2、 极限嵌套:多层 Class 的观察链传递
当业务模型极其复杂(例如:Order 包含 User,User 又包含 Address)时,普通的 @State 或 @Prop 只能观察到第一层(Order)的变化。要实现深层观察,每一层嵌套的 Class 都必须被 @Observed 装饰,并在组件间通过 @ObjectLink 逐层传递。
@Observed
class Address {
public city: string = 'Hangzhou';
}
@Observed
class User {
public name: string = 'Tom';
public address: Address = new Address();
}
@Entry
@Component
struct ProfilePage {
@State user: User = new User();
build() {
Column() {
// 将第二层嵌套对象直接传给子组件
AddressComponent({ address: this.user.address })
}
}
}
@Component
struct AddressComponent {
@ObjectLink address: Address;
build() {
Text(`当前城市: ${this.address.city}`)
.onClick(() => {
// 修改第三层属性,依然能触发 UI 刷新
this.address.city = 'Beijing';
})
}
}
3、 架构演进:V1 与 V2 状态管理的混用与兼容
随着 HarmonyOS API 版本的升级,官方推出了性能更优、支持深层观察的状态管理 V2(@ObservedV2 + @Param)。在实际工程中,老代码(V1)与新代码(V2)共存是常态。
混用避坑指南:
当 V2 组件需要将嵌套对象传递给 V1 的 @ObjectLink 时,由于两者的底层观察机制不同,直接传递会导致不刷新。必须使用 UIUtils.enableV2Compatibility 和 UIUtils.makeV1Observed 进行桥接转换。
import { UIUtils } from '@kit.ArkUI';
// V1 的普通类
class V1Model {
public name: string = 'Tom';
}
// V2 父组件
@ComponentV2
struct ParentV2 {
// 必须调用工具方法进行兼容转换
@Local v1Model: V1Model = UIUtils.enableV2Compatibility(
UIUtils.makeV1Observed(new V1Model())
);
build() {
Column() {
// 安全地传递给 V1 子组件
ChildV1({ model: this.v1Model })
}
}
}
// V1 子组件
@Component
struct ChildV1 {
@ObjectLink model: V1Model;
build() {
Text(this.model.name).onClick(() => {
this.model.name += '!'; // 正常触发刷新
})
}
}
- 需要父组件和子组件双向联动(如表单编辑、复杂状态共享):使用 @Observed + @ObjectLink。
- 需要父组件向子组件单向传递,且子组件有独立的本地状态副本(如列表项的展开/折叠状态):使用 @Observed + @Prop。
- 所有需要被 @ObjectLink 接收,或需要被深度观察的类,必须加上 @Observed 装饰器。
- 如果类内部嵌套了其他复杂对象,嵌套的类也必须加上 @Observed,否则内层属性的变化无法触发 UI 刷新。
- @ObjectLink 装饰的变量是只读的,禁止整体赋值(如 this.objLink = newObj),只能修改其内部属性(如 this.objLink.name = 'newName')。
- @Prop 装饰的变量允许本地赋值,但需知晓父组件更新时会覆盖本地修改。
通过合理联动这三个装饰器,开发者可以构建出数据流向清晰、UI 刷新精准且性能优异的应用架构。
如果项目是基于最新的 API 开发,且面临极其复杂的嵌套数据流,建议直接使用 状态管理 V2。V2 采用了直接数据观察机制,彻底解决了 V1 的痛点:




