【HarmonyOS 5】makeObserved接口详解
一、makeObserved接口是什么?
makeObserved 接口是鸿蒙状态管理框架(API version 12 起可用)提供的一个工具函数,用于将普通不可观察数据转换为可观察数据。它主要解决 @ObservedV2/@Trace 无法覆盖的特殊场景下的数据观察需求。
核心定位
在状态管理V2体系中,@Trace 装饰器用于标记类中需要被观察的属性。但当遇到以下情况时,@Trace 无能为力:
| 场景 | 问题描述 |
|---|---|
| 三方SDK包中的类 | 类定义在三方包中,无法手动给属性添加 @Trace |
| @Sendable装饰的类 | 该类禁止动态修改属性,@Trace 的行为不被允许 |
| JSON.parse返回的匿名对象 | 没有明确的class声明,无法使用装饰器 |
| 集合类型 | collections.Array/Set/Map等需要观察能力 |
简单理解:如果说
@Trace是"在类定义时就声明哪些属性可观察",那makeObserved就是"在运行时把一个普通对象变成可观察的"。
与V1/V2状态管理的关系
-
适用于V2场景:
makeObserved主要服务于@ComponentV2+@Local/@Param的状态管理体系 -
不能与V1混用:与
@State、@Prop等V1装饰器一起使用会抛出运行时异常
二、基本使用
导入与调用
typescript
import { UIUtils } from '@kit.ArkUI';
class UserInfo {
id: number = 0;
name: string = '';
}
// 将普通对象变为可观察数据
let observedUser: UserInfo = UIUtils.makeObserved(new UserInfo());
在组件中使用
typescript
@Entry
@ComponentV2
struct Demo {
@Local user: UserInfo = UIUtils.makeObserved(new UserInfo());
build() {
Column() {
Text(`用户名: ${this.user.name}`)
Button('修改名称').onClick(() => {
this.user.name = '新名称'; // 自动触发UI刷新
})
}
}
}
三、适用场景详解
场景一:处理JSON.parse返回的数据
这是最典型的使用场景——从网络请求获取JSON数据后,需要让它在UI中可观察。
typescript
import { UIUtils } from '@kit.ArkUI';
interface UserData {
name: string;
age: number;
email: string;
}
@Entry
@ComponentV2
struct JsonDemo {
private rawJson: string = '{"name": "Alice", "age": 25, "email": "alice@example.com"}';
@Local observedData: UserData = UIUtils.makeObserved(
JSON.parse(this.rawJson) as UserData
);
build() {
Column() {
Text(`姓名: ${this.observedData.name}`)
Text(`年龄: ${this.observedData.age}`)
Button('年龄+1').onClick(() => {
this.observedData.age++; // ✅ UI自动刷新
})
}
}
}
场景二:与@Sendable配合使用(跨线程数据)
@Sendable 装饰的类可以在子线程中处理数据,但返回主线程后需要 makeObserved 使其重新具备观察能力。
typescript
import { taskpool } from '@kit.ArkTS';
import { UIUtils } from '@kit.ArkUI';
@Sendable
class UserInfo {
userId: number = 0;
username: string = 'Guest';
score: number = 0;
}
@Concurrent
function processDataInThread(userId: number): UserInfo {
let result = new UserInfo();
result.userId = userId;
result.score = Math.floor(Math.random() * 100);
return result;
}
@Entry
@ComponentV2
struct SendableDemo {
@Local user: UserInfo = UIUtils.makeObserved(new UserInfo());
build() {
Column() {
Text(`分数: ${this.user.score}`)
Button('从子线程加载数据').onClick(() => {
taskpool.execute(processDataInThread, 1001).then((data: UserInfo) => {
// ⚠️ 关键:子线程返回的数据需要重新makeObserved
this.user = UIUtils.makeObserved(data);
});
})
}
}
}
注意:
makeObserved的返回值不能直接传给子线程,传递回子线程时需使用原始数据。
场景三:处理集合类型
支持 Array、Map、Set 及 collections 包下的容器类型。
typescript
import { collections } from '@kit.ArkTS';
@Entry
@ComponentV2
struct CollectionDemo {
@Local list: Array<string> = UIUtils.makeObserved(['a', 'b', 'c']);
@Local map: Map<string, number> = UIUtils.makeObserved(new Map());
build() {
Column() {
ForEach(this.list, (item) => {
Text(item)
})
Button('添加元素').onClick(() => {
this.list.push('d'); // ✅ push操作可触发UI刷新
this.map.set('key', 100); // ✅ set操作可触发UI刷新
})
}
}
}
可触发UI刷新的集合API:
| 类型 | 可观察的API |
|---|---|
| Array | push, pop, shift, unshift, splice, copyWithin, fill, reverse, sort |
| Map/collections.Map | set, clear, delete |
| Set/collections.Set | add, clear, delete |
| Date | setFullYear, setMonth, setDate, setHours, ... |
四、关键限制与注意事项
1. 参数类型限制
| 参数类型 | 行为 |
|---|---|
| Object类型实例 | ✅ 正常处理,返回可观察代理 |
| 非Object类型(number/string等) | ❌ 编译报错 |
| undefined / null | ⚠️ 返回自身,不做处理 |
| 已被观察的数据(双重代理) | ⚠️ 直接返回原对象,不做处理 |
typescript
// ❌ 错误用法
let res1 = UIUtils.makeObserved(123); // 编译报错
let res2 = UIUtils.makeObserved(undefined); // res2 === undefined
// ⚠️ 双重代理——直接返回,不做处理
@ObservedV2
class Info { @Trace id: number = 0; }
let info = new Info();
let observed = UIUtils.makeObserved(info); // 返回原对象,不处理
2. 嵌套对象需要逐层包裹(最重要的坑)
makeObserved 是浅层包裹,嵌套对象内部的变化不会被外层感知。
typescript
class Address {
city: string = '北京';
}
class User {
name: string = '张三';
address: Address = new Address(); // 这里没有makeObserved
}
@Entry
@ComponentV2
struct Demo {
@Local user: User = UIUtils.makeObserved(new User());
build() {
Column() {
Text(this.user.address.city)
Button('修改城市').onClick(() => {
this.user.address.city = '上海'; // ❌ UI不会刷新!
})
}
}
}
正确做法:每一层都要包裹。
typescript
// 方案一:构造函数中处理
class Address {
city: string = '北京';
}
class User {
name: string = '张三';
address: Address;
constructor() {
this.address = UIUtils.makeObserved(new Address());
}
}
// 方案二:创建时递归包裹
let user = new User();
user.address = UIUtils.makeObserved(new Address());
let observedUser = UIUtils.makeObserved(user);
3. 不能与V1状态装饰器混用
typescript
// ❌ 运行时异常 @State message: Info = UIUtils.makeObserved(new Info());
@State 本身会创建代理,与 makeObserved 的代理机制冲突。
4. getTarget问题
通过 getTarget 获取原始对象后修改属性,不会触发UI刷新——必须操作代理对象。
五、工程化建议
封装工厂函数
typescript
function createObserved<T>(obj: T): T {
if (obj === null || obj === undefined) {
return obj;
}
return UIUtils.makeObserved(obj);
}
// 用于创建带观察能力的模型实例
function createModel<T>(Constructor: new () => T): T {
const instance = new Constructor();
// 递归处理嵌套对象需要额外实现
return UIUtils.makeObserved(instance);
}
统一数据层处理
在网络请求层统一使用 makeObserved 包裹返回数据,避免业务代码中遗漏。
六、总结对比
| 特性 | @ObservedV2/@Trace | makeObserved |
|---|---|---|
| 适用场景 | 自有类,可修改源码 | 三方类、匿名对象、@Sendable类 |
| 使用方式 | 装饰器语法 | 函数调用 |
| 嵌套支持 | 自动递归 | 需手动逐层包裹 |
| 性能 | 较好 | 有额外代理开销 |
| 灵活性 | 需修改类定义 | 无需修改原类 |
核心原则:makeObserved 不是"可选功能",而是在V2状态管理体系下,让非观察数据具备响应式能力的必要手段。优先使用 @ObservedV2/@Trace,在无法使用的场景下再选择 makeObserved。
更多推荐



所有评论(0)