ArkTS 中 @Observed 和 @ObjectLink 装饰器总结
·
ArkTS 中 @Observed 和 @ObjectLink 装饰器总结
📌 快速概览
@Observed 和 @ObjectLink 是 ArkTS 中用于嵌套对象深度观察的装饰器组合,解决了 @State、@Prop、@Link 只能观察第一层属性变化的问题。
🎯 一、核心概念
1.1 问题背景
@State 的局限性:只能观察第一层属性变化
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
@Entry
@Component
struct TestPage {
@State person: Person = new Person('张三', 25)
build() {
Column() {
Text(`姓名: ${this.person.name}`)
Text(`年龄: ${this.person.age}`)
Button('修改姓名')
.onClick(() => {
// ❌ 不会触发 UI 更新(只观察第一层)
this.person.name = '李四'
})
Button('整体替换')
.onClick(() => {
// ✅ 会触发 UI 更新(第一层变化)
this.person = new Person('李四', 30)
})
}
}
}
问题:
@State只观察第一层属性(this.person)- 修改嵌套属性(
this.person.name)不会触发 UI 更新 - 必须整体替换对象才能触发更新,不够灵活
1.2 @Observed 装饰器
定义:类装饰器,用于修饰类,使类的实例变为可观察对象。
作用:
- 将普通类转换为响应式对象
- 使对象的所有属性都可被观察
- 支持深度观察(嵌套对象)
语法:
@Observed
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
1.3 @ObjectLink 装饰器
定义:变量装饰器,用于修饰子组件中的变量,接收父组件传递的 @Observed 对象。
作用:
- 接收
@Observed对象 - 深度观察对象的所有属性变化
- 实现父子组件双向数据同步
语法:
@Component
struct ChildComponent {
@ObjectLink person: Person // 接收 @Observed 对象
build() {
Column() {
Text(this.person.name)
}
}
}
1.4 工作原理
1. 父组件用 @State/@Prop/@Link 持有 @Observed 对象
↓
2. 将对象传递给子组件的 @ObjectLink 变量
↓
3. 子组件通过 @ObjectLink 深度观察对象的所有属性
↓
4. 任何属性变化都会触发 UI 更新
核心要点:
@Observed:修饰类,让类的实例可被观察@ObjectLink:修饰变量,接收可观察对象并深度观察- 必须配合使用:
@Observed类 +@ObjectLink变量
📖 二、基本用法
2.1 简单示例
// 步骤 1:定义 @Observed 类
@Observed
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
// 步骤 2:父组件用 @State 持有对象
@Entry
@Component
struct ParentPage {
@State person: Person = new Person('张三', 25)
build() {
Column() {
Text('父组件')
Text(`姓名: ${this.person.name}`)
Text(`年龄: ${this.person.age}`)
// 步骤 3:传递给子组件
ChildComponent({ person: this.person })
Button('父组件修改姓名')
.onClick(() => {
// ✅ 触发 UI 更新
this.person.name = '李四'
})
}
}
}
// 步骤 4:子组件用 @ObjectLink 接收
@Component
struct ChildComponent {
@ObjectLink person: Person
build() {
Column() {
Text('子组件')
Text(`姓名: ${this.person.name}`)
Text(`年龄: ${this.person.age}`)
Button('子组件修改年龄')
.onClick(() => {
// ✅ 触发父子组件 UI 同步更新
this.person.age++
})
}
}
}
效果:
- ✅ 父组件修改
person.name,父子组件都会更新 - ✅ 子组件修改
person.age,父子组件都会更新 - ✅ 双向数据同步
2.2 嵌套对象示例
// 嵌套类也需要 @Observed
@Observed
class Address {
city: string
street: string
constructor(city: string, street: string) {
this.city = city
this.street = street
}
}
@Observed
class Person {
name: string
address: Address // 嵌套对象
constructor(name: string, address: Address) {
this.name = name
this.address = address
}
}
@Entry
@Component
struct ParentPage {
@State person: Person = new Person('张三', new Address('北京', '朝阳路'))
build() {
Column() {
Text(`姓名: ${this.person.name}`)
Text(`城市: ${this.person.address.city}`)
Text(`街道: ${this.person.address.street}`)
// 传递给子组件
ChildComponent({ person: this.person })
Button('修改城市')
.onClick(() => {
// ✅ 深度观察,触发 UI 更新
this.person.address.city = '上海'
})
}
}
}
@Component
struct ChildComponent {
@ObjectLink person: Person
build() {
Column() {
Button('修改街道')
.onClick(() => {
// ✅ 深度观察,触发 UI 更新
this.person.address.street = '浦东路'
})
}
}
}
要点:
- 嵌套对象的类(
Address)也需要加@Observed - 支持多层嵌套的深度观察
2.3 数组中的对象
@Observed
class Student {
name: string
score: number
constructor(name: string, score: number) {
this.name = name
this.score = score
}
}
@Entry
@Component
struct StudentList {
@State students: Student[] = [
new Student('张三', 85),
new Student('李四', 90),
new Student('王五', 78)
]
build() {
Column() {
List() {
ForEach(this.students, (student: Student, index: number) => {
ListItem() {
// 传递数组中的对象给子组件
StudentCard({ student: student, index: index })
}
}, (student: Student) => student.name)
}
Button('添加学生')
.onClick(() => {
// ✅ 数组变化,触发 UI 更新
this.students.push(new Student('赵六', 88))
})
}
}
}
@Component
struct StudentCard {
@ObjectLink student: Student // 接收数组中的对象
private index: number
build() {
Row() {
Text(`${this.index + 1}. ${this.student.name}`)
Text(`分数: ${this.student.score}`)
Button('+10')
.onClick(() => {
// ✅ 修改对象属性,触发 UI 更新
this.student.score += 10
})
}
}
}
要点:
- 数组中的对象也需要用
@Observed修饰类 @ObjectLink接收数组中的单个对象- 支持修改数组中对象的属性
🎯 三、使用场景
3.1 场景 1:表单组件
@Observed
class FormData {
username: string = ''
password: string = ''
email: string = ''
phone: string = ''
}
@Entry
@Component
struct RegisterPage {
@State formData: FormData = new FormData()
build() {
Column() {
Text('注册表单')
.fontSize(24)
.fontWeight(FontWeight.Bold)
// 用户名输入
InputField({
label: '用户名',
value: this.formData.username,
onChange: (value: string) => {
this.formData.username = value
}
})
// 密码输入
InputField({
label: '密码',
value: this.formData.password,
onChange: (value: string) => {
this.formData.password = value
}
})
// 预览组件
FormPreview({ formData: this.formData })
Button('提交')
.onClick(() => {
console.log(JSON.stringify(this.formData))
})
}
}
}
@Component
struct InputField {
private label: string
private value: string
private onChange: (value: string) => void
build() {
Column() {
Text(this.label)
TextInput({ text: this.value })
.onChange((value: string) => {
this.onChange(value)
})
}
}
}
@Component
struct FormPreview {
@ObjectLink formData: FormData
build() {
Column() {
Text('表单预览')
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(`用户名: ${this.formData.username}`)
Text(`密码: ${'*'.repeat(this.formData.password.length)}`)
}
.backgroundColor('#F5F5F5')
.padding(10)
.borderRadius(8)
}
}
3.2 场景 2:购物车
@Observed
class Product {
id: number
name: string
price: number
quantity: number
constructor(id: number, name: string, price: number) {
this.id = id
this.name = name
this.price = price
this.quantity = 1
}
get totalPrice(): number {
return this.price * this.quantity
}
}
@Entry
@Component
struct ShoppingCart {
@State products: Product[] = [
new Product(1, 'iPhone 15', 7999),
new Product(2, 'iPad Pro', 6799),
new Product(3, 'MacBook', 12999)
]
get totalAmount(): number {
return this.products.reduce((sum, p) => sum + p.totalPrice, 0)
}
build() {
Column() {
Text('购物车')
.fontSize(24)
.fontWeight(FontWeight.Bold)
List() {
ForEach(this.products, (product: Product) => {
ListItem() {
ProductItem({ product: product })
}
}, (product: Product) => product.id.toString())
}
// 总价
Row() {
Text('总计:')
Text(`¥${this.totalAmount}`)
.fontSize(24)
.fontColor('#FF0000')
.fontWeight(FontWeight.Bold)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(15)
.backgroundColor('#FFF9E6')
}
}
}
@Component
struct ProductItem {
@ObjectLink product: Product
build() {
Row() {
Column() {
Text(this.product.name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text(`¥${this.product.price}`)
.fontSize(14)
.fontColor('#FF4D4F')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
// 数量控制
Row() {
Button('-')
.onClick(() => {
if (this.product.quantity > 1) {
this.product.quantity-- // ✅ 触发 UI 更新
}
})
Text(`${this.product.quantity}`)
.width(40)
.textAlign(TextAlign.Center)
Button('+')
.onClick(() => {
this.product.quantity++ // ✅ 触发 UI 更新
})
}
Text(`¥${this.product.totalPrice}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding(15)
.backgroundColor(Color.White)
.borderRadius(8)
}
}
3.3 场景 3:用户资料编辑
@Observed
class UserProfile {
avatar: string
nickname: string
bio: string
age: number
constructor() {
this.avatar = ''
this.nickname = '未设置'
this.bio = '这个人很懒,什么都没写'
this.age = 18
}
}
@Entry
@Component
struct ProfilePage {
@State profile: UserProfile = new UserProfile()
build() {
Column() {
// 预览区域
ProfilePreview({ profile: this.profile })
// 编辑区域
ProfileEditor({ profile: this.profile })
Button('保存')
.margin({ top: 20 })
.onClick(() => {
console.log('保存:', JSON.stringify(this.profile))
})
}
}
}
@Component
struct ProfilePreview {
@ObjectLink profile: UserProfile
build() {
Column() {
Text('预览')
.fontSize(18)
.fontWeight(FontWeight.Bold)
Image(this.profile.avatar || 'default_avatar.png')
.width(80)
.height(80)
.borderRadius(40)
Text(this.profile.nickname)
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text(this.profile.bio)
.fontSize(14)
.fontColor(Color.Gray)
Text(`年龄: ${this.profile.age}`)
}
.backgroundColor('#F5F5F5')
.padding(20)
.borderRadius(12)
}
}
@Component
struct ProfileEditor {
@ObjectLink profile: UserProfile
build() {
Column() {
Text('编辑')
.fontSize(18)
.fontWeight(FontWeight.Bold)
TextInput({ placeholder: '昵称', text: this.profile.nickname })
.onChange((value: string) => {
this.profile.nickname = value // ✅ 实时同步到预览
})
TextInput({ placeholder: '个人简介', text: this.profile.bio })
.onChange((value: string) => {
this.profile.bio = value // ✅ 实时同步到预览
})
Row() {
Text('年龄:')
Button('-')
.onClick(() => {
if (this.profile.age > 1) {
this.profile.age-- // ✅ 实时同步到预览
}
})
Text(`${this.profile.age}`)
Button('+')
.onClick(() => {
this.profile.age++ // ✅ 实时同步到预览
})
}
}
}
}
⚠️ 四、注意事项
4.1 必须配合使用
// ❌ 错误:类没有 @Observed
class Person {
name: string
}
@Component
struct MyComponent {
@ObjectLink person: Person // ❌ 报错:Person 必须是 @Observed 类
build() {
Text(this.person.name)
}
}
正确做法:
// ✅ 正确:类必须加 @Observed
@Observed
class Person {
name: string
}
@Component
struct MyComponent {
@ObjectLink person: Person // ✅ 正确
build() {
Text(this.person.name)
}
}
4.2 不能初始化
@Component
struct MyComponent {
// ❌ 错误:@ObjectLink 不能初始化
@ObjectLink person: Person = new Person('张三', 25)
build() {
Text(this.person.name)
}
}
正确做法:
@Component
struct MyComponent {
// ✅ 正确:由父组件传递,不能初始化
@ObjectLink person: Person
build() {
Text(this.person.name)
}
}
// 父组件传递
@Entry
@Component
struct ParentPage {
@State person: Person = new Person('张三', 25)
build() {
MyComponent({ person: this.person }) // 传递对象
}
}
4.3 不能传递 null 或 undefined
@Entry
@Component
struct ParentPage {
@State person: Person | null = null
build() {
if (this.person) {
// ✅ 正确:确保不是 null
MyComponent({ person: this.person })
} else {
Text('加载中...')
}
}
}
4.4 嵌套对象都需要 @Observed
// ❌ 错误:嵌套类没有 @Observed
class Address {
city: string;
}
@Observed
class Person {
address: Address; // ❌ Address 也需要 @Observed
}
正确做法:
// ✅ 正确:嵌套类也加 @Observed
@Observed
class Address {
city: string;
}
@Observed
class Person {
address: Address; // ✅ 正确
}
4.5 数组变化的处理
@Entry
@Component
struct TodoList {
@State todos: Todo[] = []
build() {
Column() {
Button('添加')
.onClick(() => {
// ✅ 正确:push 触发数组变化
this.todos.push(new Todo('新任务'))
})
Button('删除第一个')
.onClick(() => {
// ✅ 正确:splice 触发数组变化
this.todos.splice(0, 1)
})
Button('修改第一个任务名')
.onClick(() => {
// ✅ 正确:修改对象属性触发更新
this.todos[0].title = '已修改'
})
}
}
}
📊 五、与其他装饰器对比
5.1 @State vs @ObjectLink
| 特性 | @State | @ObjectLink |
|---|---|---|
| 使用位置 | 父组件 | 子组件 |
| 观察深度 | 第一层(浅观察) | 所有层(深度观察) |
| 是否初始化 | 必须初始化 | 不能初始化,由父组件传递 |
| 数据流向 | 组件内部状态 | 父子双向同步 |
| 对象类型要求 | 任意类型 | 必须是 @Observed 类 |
5.2 @Prop vs @ObjectLink
| 特性 | @Prop | @ObjectLink |
|---|---|---|
| 数据流向 | 单向(父 → 子) | 双向(父 ↔ 子) |
| 观察深度 | 第一层(浅观察) | 所有层(深度观察) |
| 是否可修改 | 子组件只读 | 子组件可读写 |
| 对象类型要求 | 任意类型 | 必须是 @Observed 类 |
| 传递方式 | 值传递(复制) | 引用传递(共享) |
5.3 @Link vs @ObjectLink
| 特性 | @Link | @ObjectLink |
|---|---|---|
| 数据流向 | 双向(父 ↔ 子) | 双向(父 ↔ 子) |
| 观察深度 | 第一层(浅观察) | 所有层(深度观察) |
| 传递方式 | 使用 $ 语法 |
直接传递对象 |
| 对象类型要求 | 任意类型 | 必须是 @Observed 类 |
| 使用复杂度 | 需要 $ 语法 |
直接传递,更简洁 |
5.4 完整对比示例
@Observed
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
@Entry
@Component
struct ComparisonPage {
@State person: Person = new Person('张三', 25)
@State count: number = 0
build() {
Column() {
// 1. @State:父组件持有对象
Text(`父组件 @State: ${this.person.name}, ${this.person.age}`)
Button('父组件修改 name')
.onClick(() => {
this.person.name = '李四' // ✅ 触发更新
})
Divider()
// 2. @Prop:单向传递(值传递)
PropChild({ person: this.person })
Divider()
// 3. @Link:双向绑定(需要 $ 语法)
LinkChild({ count: $count })
Divider()
// 4. @ObjectLink:双向绑定(深度观察)
ObjectLinkChild({ person: this.person })
}
}
}
@Component
struct PropChild {
@Prop person: Person
build() {
Column() {
Text(`@Prop 子组件: ${this.person.name}`)
Button('修改 name (无效)')
.onClick(() => {
// ❌ @Prop 是只读的,修改不会影响父组件
this.person.name = '王五'
})
}
}
}
@Component
struct LinkChild {
@Link count: number
build() {
Column() {
Text(`@Link 子组件: ${this.count}`)
Button('修改 count')
.onClick(() => {
this.count++ // ✅ 父子双向同步
})
}
}
}
@Component
struct ObjectLinkChild {
@ObjectLink person: Person
build() {
Column() {
Text(`@ObjectLink 子组件: ${this.person.name}, ${this.person.age}`)
Button('修改 age')
.onClick(() => {
this.person.age++ // ✅ 父子双向同步(深度观察)
})
}
}
}
🎯 六、最佳实践
6.1 什么时候使用 @ObjectLink?
✅ 使用场景:
-
嵌套对象的深度观察
- 需要观察对象内部属性的变化
- 对象有多层嵌套结构
-
复杂对象的父子同步
- 需要父子组件同步修改对象
- 对象有多个属性需要同步
-
列表中的对象
- 数组中的对象需要单独传递给子组件
- 需要修改数组中对象的属性
-
表单数据管理
- 表单有多个字段
- 需要实时预览表单数据
❌ 不适用场景:
-
简单类型数据
- 数字、字符串、布尔值等:使用
@Prop或@Link
- 数字、字符串、布尔值等:使用
-
只读数据
- 子组件不需要修改数据:使用
@Prop
- 子组件不需要修改数据:使用
-
单层对象
- 对象只有一层属性:使用
@Link
- 对象只有一层属性:使用
6.2 代码组织建议
// ========== models.ts ==========
// 统一管理所有 @Observed 类
@Observed
export class User {
id: number
name: string
profile: UserProfile
constructor(id: number, name: string) {
this.id = id
this.name = name
this.profile = new UserProfile()
}
}
@Observed
export class UserProfile {
avatar: string = ''
bio: string = ''
age: number = 18
}
// ========== ParentPage.ets ==========
import { User } from './models'
@Entry
@Component
struct ParentPage {
@State user: User = new User(1, '张三')
build() {
Column() {
ChildComponent({ user: this.user })
}
}
}
// ========== ChildComponent.ets ==========
import { User } from './models'
@Component
export struct ChildComponent {
@ObjectLink user: User
build() {
Column() {
Text(this.user.name)
}
}
}
🎓 七、快速记忆
核心要点
- @Observed:修饰类,让类的实例可被深度观察
- @ObjectLink:修饰变量,接收可观察对象
- 必须配合使用:
@Observed+@ObjectLink - 解决问题:@State 只能观察第一层,@ObjectLink 可以深度观察
- 双向同步:子组件修改对象,父组件也会同步更新
使用步骤
1. 定义类,加 @Observed 装饰器
↓
2. 父组件用 @State 持有对象
↓
3. 传递对象给子组件
↓
4. 子组件用 @ObjectLink 接收
↓
5. 任意组件修改对象属性,都会触发双向更新
记忆口诀
Observed 修饰类,对象可观察
ObjectLink 接收它,深度来同步
父子共享引用,双向自动更新
嵌套对象必备,表单列表首选
📌 八、总结
何时使用?
| 场景 | 推荐方案 |
|---|---|
| 简单类型(数字、字符串) | @Prop / @Link |
| 对象的第一层属性 | @Link |
| 对象的嵌套属性(深度观察) | @ObjectLink |
| 列表中的对象 | @ObjectLink |
| 复杂表单数据 | @ObjectLink |
| 需要深度观察的购物车、用户信息 | @ObjectLink |
核心优势
- ✅ 深度观察:解决 @State/@Link 只能观察第一层的问题
- ✅ 双向同步:父子组件自动同步,无需手动处理
- ✅ 简洁语法:直接传递对象,不需要
$语法 - ✅ 性能优化:只有变化的属性才会触发更新
完! 🚀
更多推荐


所有评论(0)