一、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 的返回值不能直接传给子线程,传递回子线程时需使用原始数据。

场景三:处理集合类型

支持 ArrayMapSet 及 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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐