HarmonyOS NEXT / HarmonyOS 5 ArkUI 状态管理系列
本文适用版本:API12 ~ API19(HarmonyOS NEXT)

前言

相信很多刚接触 HarmonyOS ArkUI 的开发者,都遇到过这样一个问题:

例如我们有这样一个对象:


class User {
  name: string = "Tom"
  age: number = 18
}

页面这样写:


@State
user: User = new User()

然后修改:


this.user.name = "Jack"

结果……

页面没有刷新!

很多人第一反应:

不是@State吗?为什么不刷新?

随后开始尝试:

  • this.user = new User()
  • this.user = Object.assign(...)
  • this.user = JSON.parse(JSON.stringify(...))
  • this.user = {...this.user}

终于刷新了。

于是很多人认为:

HarmonyOS只能修改整个对象。

其实……

并不是。

真正原因是:

ArkUI 默认只能监听对象引用变化,而不会监听对象内部属性变化。

这就是今天要讲的:

@Observed + @ObjectLink

它们就是 HarmonyOS 官方提供的:

对象属性级响应式刷新机制。

很多企业项目:

  • IM聊天
  • 通讯录
  • 购物车
  • 用户信息
  • 商品详情
  • 评论列表

几乎都会使用它们。

如果不会:

后期项目几乎一定踩坑。

本文将彻底讲透它们的底层原理。


一、先理解 ArkUI 为什么不能监听对象内部变化

先来看一个最经典例子。

示例一


class User {
  name: string = "Tom"
}

@Entry
@Component
struct Index {

  @State
  user: User = new User()

  build() {

    Column() {

      Text(this.user.name)

      Button("修改姓名")
        .onClick(()=>{

          this.user.name = "Jack"

        })

    }

  }
}

很多新人觉得:

name变了。

Text应该刷新。

但是:

实际运行:


Tom

点击按钮以后:


Tom

没有变化。

为什么?


真正原因

ArkUI监听的是:


user

不是:


user.name

也就是说:


@State
↓

监听的是对象引用

例如:


user
 │
 ├──name
 ├──age
 └──sex

ArkUI只关心:


user地址有没有变

例如:


0x0012456

如果:


this.user.name="Jack"

那么:

内存其实变成:


0x0012456

name

Tom

↓

Jack

对象地址:


没有变

所以:

ArkUI认为:


没有变化

因此:

不会重新build。


二、真正触发刷新的是什么?

很多人一直误认为:

修改变量。

页面刷新。

实际上:

真正刷新的是:

对象引用变化。

例如:


this.user = new User()

或者:


this.user = {
  name:"Jack"
}

引用发生变化:


旧对象

0x001111

↓

新对象

0x009999

ArkUI检测到:


地址变了

于是:

重新build。

流程如下:


赋值

↓

引用变化

↓

State通知

↓

MarkDirty

↓

重新build

↓

Diff

↓

Render

这就是整个刷新流程。


三、为什么 Vue 可以监听?

很多人学习 HarmonyOS 前都学过:

Vue3。

Vue里面:


user.name="Jack"

页面刷新。

为什么?

因为:

Vue底层用了:


Proxy

例如:


let user = new Proxy(obj,{
   set(){
      update()
   }
})

每修改一个属性:

都会进入:


set()

然后:

通知页面。

例如:


user.name="Jack"

↓

Proxy.set

↓

update()

↓

重新render

所以:

Vue天然支持:

对象属性监听。


而 ArkUI 默认并不是这样。

默认情况下:


@State

不会给对象建立:


Proxy

所以:

属性变化:

没人知道。


四、HarmonyOS 为什么没有默认监听所有对象?

很多开发者问:

既然 Vue 可以。

HarmonyOS 为什么不全部监听?

答案:

性能。

假设:

一个购物车:


class Cart{

   goodsList:Goods[]

}

里面:


10000个商品

每个商品:


20多个字段

例如:


商品

价格

库存

图片

规格

销量

活动

品牌

...

如果:

全部Proxy。

那么:

初始化时:

需要:


10000

×

20

=

200000

个属性

全部建立监听。

这会导致:

初始化成本巨大。

尤其:

企业项目:


订单

↓

商品

↓

SKU

↓

活动

↓

库存

↓

优惠券

可能:

嵌套五六层。

如果:

全部递归Proxy:

第一次进入页面:

就会卡顿。

因此:

HarmonyOS采用:

按需监听。

需要监听:

你告诉它:


@Observed

不用监听:

普通Class。

这样:

性能更高。


五、@Observed 到底是什么?

官方解释:

使一个Class成为可观察对象。

一句话:

就是:

给整个对象增加属性监听能力。

例如:

普通类:


class User{

}

不能监听。

改成:


@Observed
class User{

}

立即变成:


Observable Object

也就是说:

以后:


name

变化:


age

变化:


address

变化:

都可以通知UI。


例如:


@Observed
class User{

  name:string="Tom"

  age:number=18

}

这时候:


name

不是普通变量。

而是:


Observable Property

内部会拥有:


getter

setter

或者:

Proxy能力。

于是:


this.user.name="Jack"

会触发:


notifyPropertyChanged()

然后:

通知:

所有引用它的组件。


六、仅有 @Observed 能刷新页面吗?

答案:

不能。

这是整个 HarmonyOS 状态管理中最容易踩的坑。

很多人写成:


@Observed
class User {

  name:string="Tom"

}

然后:


@State
user:User=new User()

点击:


this.user.name="Jack"

结果:

还是没刷新。

为什么?

因为:

虽然:


User

已经:

可以发送通知。

但是:

没有任何组件接收这个通知。

就像:


广播站

一直广播

但是:

没人收听。

广播:

没有意义。

所以:

还需要:


@ObjectLink

它负责:

订阅 @Observed 对象发送的属性变化通知,并与父组件保持对象引用同步。

也就是说:


@Observed

负责发送通知

↓

@ObjectLink

负责接收通知

↓

组件刷新

二者缺一不可。


本篇小结

这一部分,我们完成了几个最核心的知识点:

  • 为什么 @State 默认不能监听对象内部属性变化。
  • ArkUI 默认监听的是对象引用,而不是对象成员。
  • 为什么重新赋值可以刷新,而修改成员不会刷新。
  • HarmonyOS 为什么没有像 Vue 一样默认代理所有对象(性能考虑)。
  • @Observed 的职责:让类具备属性变化通知能力
  • 为什么只有 @Observed 还不够,还需要 @ObjectLink 建立组件与对象之间的响应式连接。

七、为什么 @Observed 和 @ObjectLink 必须一起使用?

很多开发者都会问:

既然 @Observed 已经可以监听对象属性变化了,为什么还需要 @ObjectLink

我们先来看官方推荐的使用方式。

官方标准写法


@Observed
class User {
  name: string = 'Tom'
  age: number = 18
}

父组件:


@Entry
@Component
struct ParentPage {

  @State
  user: User = new User()

  build() {
    Column() {

      Child({
        user: this.user
      })

      Button("修改姓名")
        .onClick(() => {
          this.user.name = "Jack"
        })

    }
  }
}

子组件:


@Component
struct Child {

  @ObjectLink
  user: User

  build() {
    Text(this.user.name)
  }
}

运行以后:

第一次进入页面:


Tom

点击按钮:


Jack

整个过程:


父组件

↓

修改对象属性

↓

Observed通知

↓

ObjectLink收到通知

↓

Child刷新

↓

Text更新

整个刷新过程中:

没有重新创建User对象。


八、如果不用 @ObjectLink 会发生什么?

例如:

很多人这样写:


@Component
struct Child {

    @Prop
    user: User

    build() {
        Text(this.user.name)
    }

}

或者:


@Component
struct Child {

    @State
    user: User

}

甚至:


@Component
struct Child {

    user: User

}

都会出现一个现象:

父组件修改:


this.user.name = "Jack"

子组件:


没有刷新

为什么?

因为:

这些装饰器:

都没有建立:


对象监听关系

只有:


ObjectLink

↓

订阅Observed对象

所以:

官方明确规定:


Observed

+

ObjectLink

配套使用

九、@ObjectLink 到底是什么?

很多人误认为:

ObjectLink

=

Link

实际上:

完全不是一个东西。

它真正作用:

建立组件和Observed对象之间的数据观察关系。

可以理解成:


Observed

负责广播

↓

ObjectLink

负责订阅

例如:


User

↓

name变化

↓

广播通知

↓

ObjectLink收到

↓

组件刷新

如果没有ObjectLink:

广播没人接收。


可以理解成消息订阅模型

例如:

微信群。

发送消息:


张三:

大家晚上聚餐。

如果:

没人进群。

消息:

没有意义。

ObjectLink:

就是:


加入群聊

之后:

只要:

User对象:

发生变化。

ObjectLink:

立即收到。

然后:

通知UI刷新。


十、@ObjectLink 底层到底保存了什么?

实际上:

它保存的:

不是对象。

而是:


对象引用

+

监听关系

例如:

父组件:


0x00112233

User

子组件:


ObjectLink

↓

0x00112233

注意:

这里:

没有复制对象。

而是:


共同引用

同一个User

所以:

父组件:


user.name="Jack"

子组件:

立即就是:


Jack

因为:

它们:

操作的是:

同一块内存。

内存示意图:


Parent

↓

0x001000

↑

↓

Child

共同指向:


User

而不是:


Parent

↓

User①

Child

↓

User②

所以:

ObjectLink:

是真正意义上的:


双向引用

十一、ObjectLink 为什么叫 Link?

因为:

它建立的是:


Link

链接

绑定

关联

不是:

复制。

例如:


Parent

↓

User

↑

↓

Child

始终:

同一个对象。

所以:

Child:

修改:


this.user.name="Lucy"

Parent:

立即就是:


Lucy

无需:

任何:

callback。

无需:

emit。

无需:

EventHub。

无需:

重新赋值。


十二、ObjectLink 双向同步演示

父组件:


@Observed
class User{
  name:string="Tom"
}

@Entry
@Component
struct Index{

  @State
  user:User=new User()

  build(){

    Column(){

      Text("父:"+this.user.name)

      Child({
        user:this.user
      })

    }

  }

}

子组件:


@Component
struct Child{

  @ObjectLink
  user:User

  build(){

    Column(){

      Text("子:"+this.user.name)

      Button("修改")
      .onClick(()=>{

          this.user.name="Lucy"

      })

    }

  }

}

运行:


父:Tom

子:Tom

点击:


修改

结果:


父:Lucy

子:Lucy

整个过程中:

父组件:

没有:


setState

没有:


重新赋值

依旧:

同步。

这就是:

ObjectLink:

最大的价值。


十三、ObjectLink 的刷新流程

很多文章:

到这里:

就结束了。

实际上:

真正重要的是:

刷新流程。

例如:

点击按钮:


this.user.name="Lucy"

到底:

发生什么?

整个流程:


点击Button

↓

修改属性

↓

setter()

↓

Observed检测变化

↓

通知SubscriberManager

↓

找到ObjectLink

↓

ObjectLink通知Component

↓

Component Dirty

↓

ArkUI重新build

↓

Diff

↓

RenderNode更新

↓

GPU绘制

企业项目:

理解这一流程:

非常重要。

因为:

后面:

性能优化:

全部基于这里。


十四、Subscriber(订阅者)到底是什么?

ArkUI内部:

其实:

维护了一张:


订阅关系表

类似:


User①

↓

Child①

↓

Child②

↓

Child③

例如:

三个页面:

同时:

引用:

同一个User。


Profile

↓

User

↑

Friend

↑

Chat

User:

修改:


name

系统:

立即:

找到:


三个Subscriber

然后:

全部刷新。

所以:

一个对象:

可以:

通知:

多个组件。

这也是:

企业IM、

企业通讯录、

企业CRM:

大量使用ObjectLink的原因。


十五、多个 ObjectLink 可以同时绑定一个对象吗?

答案:

可以,而且非常常见。

例如:


主页

↓

User

↑

消息中心

↑

我的页面

↑

设置页面

四个页面:

全部:

ObjectLink。

修改:


头像

立即:

四个地方:

一起刷新。

无需:


EventHub

Emitter

通知广播

手动刷新

这就是:

响应式数据模型。


十六、ObjectLink 与 @Link 的本质区别

很多初学者最容易混淆这两个装饰器,它们虽然名字都带 Link,但解决的问题完全不同。

对比项 @Link @ObjectLink
绑定普通变量
绑定 @Observed 对象
支持对象属性监听
双向修改对象成员
引用同一个对象 部分场景
推荐用于嵌套对象

举个例子:


@State
count: number = 1

子组件:


@Link
count: number

这是 @Link 最典型的使用场景。

而:


@Observed
class User {
  name: string = ""
}

则应该使用:


@ObjectLink
user: User

如果误用 @Link 来接收 User,对象成员变化不会触发子组件响应式刷新。


本篇小结

这一部分重点讲解了:

  • 为什么 @Observed@ObjectLink 必须配套使用。
  • @ObjectLink 本质上是 观察者(Subscriber),负责订阅 @Observed 对象。
  • @ObjectLink 保存的是对象引用而不是对象副本,因此父子组件共享同一个实例。
  • @ObjectLink 支持真正的双向同步,无需事件总线、回调或重新赋值。
  • ArkUI 内部通过 订阅关系(Subscriber) 完成对象属性变化后的组件刷新。

十七、为什么加了 @Observed 还是不刷新?

这是很多开发者遇到最多的问题。

代码:


@Observed
class User {

  name:string = "Tom"

  address:Address = new Address()

}


@Observed
class Address {

  city:string = "Tokyo"

}

页面:


@State
user:User = new User()

修改:


this.user.address.city = "Osaka"

结果:

页面:

不刷新。

很多人疑惑:

User 有 @Observed,Address 也有 @Observed,为什么还是不行?

原因:

对象监听具有层级关系。


十八、理解嵌套对象监听模型

我们先看对象结构:


User

 |
 |
 +---- name

 |
 |
 +---- address

              |
              |
              +---- city

实际上:

这里存在三个监听关系:

第一层:


User

第二层:


Address

第三层:


city

如果想监听:


user.address.city

必须:

每一层:

都是可观察对象。

也就是:


@Observed
class User {

}


@Observed
class Address {

}

缺少任何一层:

都会失效。


十九、错误示例:只给外层加 @Observed

例如:


@Observed
class User {

  address:Address = new Address()

}


class Address {

  city:string="Tokyo"

}

修改:


this.user.address.city="Osaka"

不会刷新。

原因:

User知道:


address变化

但是不知道:


address.city变化

因为:

Address不是Observed对象。


二十、正确写法:所有需要观察的层级都加 @Observed

正确:


@Observed
class Address {

  city:string="Tokyo"

}


@Observed
class User {

  name:string="Tom"

  address:Address = new Address()

}

然后:

组件:


@Component
struct Child {

  @ObjectLink
  user:User


  build(){

    Text(this.user.address.city)

  }

}

修改:


this.user.address.city="Osaka"

刷新流程:


city变化

↓

Address setter

↓

Address通知

↓

User关联对象收到

↓

ObjectLink收到

↓

Child刷新

↓

Text更新

二十一、嵌套对象最大的坑:对象初始化顺序

例如:

错误:


@Observed
class User {

  address?:Address

}

然后:


this.user.address =
{
 city:"Tokyo"
}

问题:

这个对象:

不是Observed实例。

它只是普通Object。

也就是说:

你虽然定义:


@Observed
class Address

但是实际:

赋值的是:


{
 city:"Tokyo"
}

不是:


new Address()

正确:


this.user.address =
new Address()

或者:


class Address {

 city:string

 constructor(city:string){

   this.city=city

 }

}

然后:


new Address("Tokyo")

二十二、为什么对象字面量经常导致刷新失败?

很多JavaScript开发习惯:

喜欢:


const user={
 name:"Tom",
 age:18
}

但是:

ArkUI状态管理:

更加推荐:


class User{

 name:string="Tom"

 age:number=18

}

原因:

class可以:

被:


@Observed

装饰。

而:

object:

没有生命周期。

例如:

普通对象:


{
 name:"Tom"
}

内部:

没有:


observer

subscriber

notify

所以:

无法通知UI。


二十三、@Observed 不会递归监听普通对象

这是一个非常重要的知识点。

很多人认为:


@Observed
class User{

 data:any

}

然后:

data里面所有东西:

自动监听。

错误。

例如:


@Observed
class User{

  info={
     name:"Tom",
     age:18
  }

}

修改:


this.user.info.name="Jack"

不会刷新。

原因:

info:

只是普通Object。

结构:


User

Observed

 |

 +---- info

        普通Object

        |
        +----name

        +----age

监听链:

断了。


二十四、正确处理复杂对象

方案一:

全部Class化。

例如:


@Observed
class UserInfo{

 name:string="Tom"

 age:number=18

}


@Observed
class User{

 info:UserInfo=new UserInfo()

}

这样:

完整响应链:


User

↓

UserInfo

↓

name

全部可观察。


方案二:

使用状态管理提供的复杂对象能力。

HarmonyOS 新版本:

针对复杂对象:

提供了:


makeV1Observed

等能力。

适用于:

已有业务Model。

例如:

服务器返回:


{
 "user":{
   "name":"Tom",
   "address":{
      "city":"Tokyo"
   }
 }
}

不方便全部改成class。

可以:

转换成可观察对象。


二十五、List中使用 @Observed + @ObjectLink

企业项目最常见:

列表。

例如:

通讯录:


[
 {
   name:"Tom"
 },
 {
   name:"Jack"
 }
]

错误方式:


@State
users:Array<User>

然后:

修改:


users[0].name="Lucy"

可能:

无法刷新。


原因:

数组监听:

和对象监听:

是两个层次。

数组:

监听:


push

pop

splice

替换

但是:

不一定监听:


item.name

二十六、正确列表模型

定义:


@Observed
class Contact{

 name:string=""

 phone:string=""

}

页面:


@State
contacts:Array<Contact>=[]

ForEach:


ForEach(
 this.contacts,
 (item:Contact)=>{

   ContactItem({
      contact:item
   })

 }
)

子组件:


@Component
struct ContactItem{


 @ObjectLink
 contact:Contact


 build(){

   Text(this.contact.name)

 }

}

修改:


this.contacts[0].name="Lucy"

结果:

对应item刷新。

而不是:

整个List刷新。


二十七、为什么 ObjectLink 对 List 性能更好?

假设:

通讯录:

10000条。

如果:

修改第一条:

普通方式:


整个List刷新

10000个Item重新build

性能:

差。


ObjectLink:

流程:


Contact对象变化

↓

找到对应ObjectLink

↓

刷新第一个Item

结果:


1个Item刷新

这就是:

大型应用:

必须使用:

对象观察。


二十八、对象数组刷新最佳实践

推荐:


@Observed
class Product {

 id:number

 name:string

 price:number

}

数据:


@State
products:Product[]=[]

Item:


@Component
struct ProductItem {


 @ObjectLink
 product:Product


 build(){

   Row(){

    Text(this.product.name)

    Text(
      this.product.price.toString()
    )

   }

 }

}

修改:


product.price=99

只刷新:

当前商品。


二十九、常见错误总结

错误1

只加:


@Observed

没有:


@ObjectLink

结果:

对象变化:

没人接收。


错误2

嵌套对象没有全部Observed。

错误:


@Observed
class User{

 address:{
   city:string
 }

}

错误3

使用普通Object。

错误:


this.user={
 name:"Tom"
}

正确:


new User()

错误4

List直接修改item属性。

错误:


list[0].name="Jack"

没有Observed。


错误5

复制对象。

例如:


childUser={
 ...this.user
}

这样:

响应链断开。


三十、本篇总结

本篇核心:

1. @Observed不是万能监听

它只监听:

Observable对象。


2. 嵌套对象必须完整观察链

例如:


User

↓

Address

↓

City

每一层:

都需要支持观察。


3. 普通Object不会自动监听

推荐:

Class模型。


4. List场景必须结合ObjectLink

实现:

精准刷新。


5. 企业项目推荐数据结构


Model(Class)

        ↓

@Observed

        ↓

Page(@State)

        ↓

Component(@ObjectLink)

        ↓

精准刷新

三十一、企业项目为什么大量使用 @Observed + @ObjectLink?

在简单Demo中:


Text(this.user.name)

看不出优势。

但是企业项目:

数据结构通常是:


用户

 ├── 基础信息

 ├── 头像

 ├── VIP状态

 ├── 地址

 ├── 订单列表

 ├── 消息列表

 └── 权限列表

一个页面:

可能同时展示:


头像

昵称

等级

订单数量

优惠券数量

消息数量

如果使用:


@State
user:User

修改:


user.avatar

很容易导致:

整个页面刷新。


而:


@Observed
+
@ObjectLink

可以做到:


头像变化

↓

Avatar组件刷新


昵称不变

↓

不刷新


订单不变

↓

不刷新

这就是:

细粒度响应式更新。


三十二、案例一:仿微信聊天列表

先设计数据模型。

聊天消息:


@Observed
class Message {


  id:number = 0


  content:string = ""


  time:string = ""


  unread:number = 0


}

聊天对象:


@Observed
class Conversation {


  userName:string=""


  avatar:string=""


  lastMessage:Message =
    new Message()


}

这里注意:

两个类:

都有:


@Observed

原因:

存在嵌套关系:


Conversation

        |

        |

     Message

        |

        |

     unread

如果:

修改:


conversation.lastMessage.unread++

需要:

完整观察链。


三十三、页面数据

首页:


@Entry
@Component
struct ChatPage {


 @State
 conversations:Array<Conversation> = []


 build(){

   Column(){


     ForEach(
       this.conversations,
       (item:Conversation)=>{


          ChatItem({

            conversation:item

          })


       }
     )


   }


 }


}

这里:

重点:

不是:

传递数据。

而是:

传递对象引用。


三十四、聊天Item组件


@Component
struct ChatItem {


 @ObjectLink
 conversation:Conversation


 build(){

   Row(){


     Text(
       this.conversation.userName
     )


     Text(
       this.conversation.lastMessage.content
     )


     if(
       this.conversation.lastMessage.unread>0
     ){

        Text(
          this.conversation.lastMessage.unread.toString()
        )

     }


   }


 }


}

这里:

ChatItem

不会复制:

Conversation。

而是:

连接:


页面Conversation

        ↓

ObjectLink

        ↓

ChatItem

三十五、收到新消息如何刷新?

假设:

服务器收到:


Tom:
晚上吃饭吗?

更新:


conversation.lastMessage.content =
"晚上吃饭吗"


conversation.lastMessage.unread++

发生:


Message.content变化

↓

Observed通知

↓

ObjectLink收到

↓

ChatItem刷新

↓

UI更新

注意:

没有:


this.conversations =
[
...
]

没有:

重新创建数组。

没有:

刷新整个List。


三十六、为什么大型聊天App必须这样设计?

假设:

微信:

500个聊天。

每个:

一个Item。

如果:

收到一条消息:

传统方式:


修改数据

↓

刷新List

↓

500个Item重新build

成本:

巨大。


ObjectLink:


Message变化

↓

找到对应Conversation

↓

找到对应ChatItem

↓

刷新一个

成本:

O(1)。


三十七、案例二:通讯录实现

通讯录:

结构:


Contact

 |

 +-- name

 +-- phone

 +-- avatar

 +-- company

 +-- department

定义:


@Observed
class Contact{


 id:number=0


 name:string=""


 phone:string=""


 avatar:string=""


}

列表:


@State
contacts:Array<Contact>=[]

Item:


@Component
struct ContactItem{


 @ObjectLink
 contact:Contact


 build(){


   Row(){


    Image(
      this.contact.avatar
    )


    Text(
      this.contact.name
    )


   }


 }

}

修改头像:


contact.avatar =
"https://xxx.png"

结果:

只有:

对应联系人刷新。


三十八、为什么不用@Prop?

很多人会问:

这里:

为什么不用:


@Prop
contact:Contact

原因:

@Prop:

是:


单向数据复制

流程:


Parent

↓

复制

↓

Child

而:

ObjectLink:


Parent

↓

同一个对象

↓

Child

例如:

@Prop:


父对象:

name=Tom


子对象:

name=Tom

两个值。


ObjectLink:


父

     \
      User对象

     /

子

一个对象。


三十九、案例三:购物车

购物车是最经典案例。

数据:


Cart

 |

 +-- Product[]

              |

              +-- Product

                       |

                       +-- count

                       +-- price

商品模型:


@Observed
class Product{


 id:number=0


 name:string=""


 price:number=0


 count:number=1


}

购物车:


@Observed
class Cart{


 products:Array<Product>=[]


}

页面:


@State
cart:Cart=new Cart()

商品Item:


@Component
struct ProductItem{


 @ObjectLink
 product:Product


 build(){


   Row(){


    Text(
      this.product.name
    )


    Text(
      this.product.count.toString()
    )


    Button("+")
    .onClick(()=>{


       this.product.count++


    })


   }


 }


}

点击增加数量:


count++

↓

Product setter

↓

通知

↓

ProductItem刷新

四十、购物车总价计算

例如:


总价:

商品1

+

商品2

+

商品3

很多人这样:


@State
total:number=0

然后:

手动:


total++

问题:

容易不同步。


推荐:

计算属性:


get total(){

 let result=0


 this.products.forEach(item=>{

    result +=
    item.price *
    item.count

 })


 return result

}

页面:


Text(
 this.cart.total.toString()
)

这样:

商品数量变化:

自动重新计算。


四十一、对象共享模型设计

大型项目:

不要:

每个页面创建对象。

错误:


HomePage

new User()


MinePage

new User()


OrderPage

new User()

三个User。

数据:

无法同步。


正确:

建立:

Model层。

例如:


common

 |

 models

      |

      User.ets

      Order.ets

      Message.ets

所有页面:

引用:

同一个对象。


结构:


AppStorage

       |

       UserStore

       |

       User

       |

       ObjectLink

       |

       Components

四十二、@Observed对象生命周期

这是面试重点。

例如:


@Observed
class User{

}

创建:


new User()

↓

构造函数

↓

建立观察能力

↓

进入状态树

↓

组件引用

销毁:


页面退出

↓

没有引用

↓

GC回收

注意:

@Observed:

不是:

全局对象。

它不会:

自动保存。

只是:

增强Class能力。


四十三、@Observed性能注意

虽然:

@Observed很好。

但是:

不要:

所有Model全部加。

错误:


所有Bean

↓

@Observed

后果:

大量监听。


推荐:

只给:

UI需要响应的数据。

例如:

服务器Response:

不要:


@Observed
class UserResponse{

100个字段

}

应该:

转换:


Response

↓

ViewModel

↓

Observed Model

↓

UI

四十四、企业架构推荐

大型HarmonyOS项目:

推荐:


Network

   |

DTO

   |

Repository

   |

ViewModel

   |

Observed Model

   |

ArkUI Component

例如:

服务器:


{
"name":"Tom",
"age":18
}

DTO:


interface UserDTO{

name:string

age:number

}

转换:


UserModel

然后:


@Observed
class UserModel{

name:string

age:number

}

UI:


@ObjectLink
user:UserModel

四十五、本篇总结

本篇重点:

1. 企业项目为什么需要ObjectLink

因为:

大型页面:

需要:

精准刷新。


2. Chat列表最佳实践


Conversation

↓

Message

↓

ObjectLink

↓

ChatItem

3. 通讯录、购物车都适合

尤其:

对象数组。


4. ObjectLink不是复制

它是:

对象连接。


5. 推荐架构


DTO

↓

ViewModel

↓

@Observed Model

↓

@ObjectLink Component

四十六、ArkUI 状态管理到底监听什么?

在深入 @Observed 之前,需要先明确一个核心概念:

ArkUI 状态管理不是监听数据,而是监听“数据访问关系”。

什么意思?

例如:


Text(this.user.name)

ArkUI 并不是简单记录:


user.name

而是在构建阶段建立:


组件

↓

依赖

↓

user.name

关系。

也就是说:

当:


user.name

变化:

系统知道:

哪个组件依赖它。

然后:

只刷新相关组件。


例如

页面:


build(){

 Column(){

   Text(this.user.name)

   Text(this.user.age)

 }

}

系统内部类似:


Text1

依赖:

user.name


Text2

依赖:

user.age

如果:


user.name="Jack"

那么:

刷新:


Text1

而不是:

整个页面。


这就是 ArkUI 高性能的基础。


四十七、@Observed内部监听模型

我们看:


@Observed
class User{

 name:string="Tom"

 age:number=18

}

编译阶段:

ArkTS会处理:


User

↓

Observable能力

↓

属性变化通知

概念上类似:


class User {


private _name:string


get name(){

 return this._name

}


set name(value:string){

 this._name=value

 notifyChange()

}


}

所以:

执行:


user.name="Jack"

不是简单赋值。

而是:


setter

↓

通知机制

↓

依赖更新

四十八、为什么修改普通对象不会触发?

看:


class User{

 name:string="Tom"

}

执行:


user.name="Jack"

流程:


name修改

↓

JavaScript属性改变

↓

结束

没有:


通知

↓

组件

所以:

UI不知道。


而:


@Observed
class User{

}

流程:


name修改

↓

setter

↓

notify

↓

依赖组件

↓

刷新

四十九、@State数组 + @Observed对象组合

这是企业开发最常见组合。

例如:


@Observed
class User{

 name:string=""

}

页面:


@State
users:Array<User>=[]

很多人认为:

完美。

但是:

这里有几个坑。


情况1:替换数组元素


this.users[0]=new User()

可以刷新。

因为:

数组结构变化。


情况2:修改对象属性


this.users[0].name="Jack"

是否刷新?

答案:

取决于:

Item是否使用:


@ObjectLink

错误:


ForEach(
 this.users,
 (item)=>{

 Text(item.name)

 }
)

可能:

刷新不符合预期。


正确:

拆组件。


父:


ForEach(
 this.users,
 (item)=>{

 UserItem({
   user:item
 })

 }
)

子:


@Component
struct UserItem{

 @ObjectLink
 user:User


 build(){

   Text(this.user.name)

 }

}

这样:

稳定。


五十、为什么ForEach必须有key?

很多人:

写:


ForEach(
 list,
 item=>{

 }
)

简单数据:

没问题。

复杂对象:

容易出现:

刷新错乱。


例如:

列表:


A
B
C

删除:

B。

如果没有key:

系统可能认为:


第2个位置

还是原来的组件

导致:

UI复用错误。


推荐:


ForEach(
 this.users,
 (item:User)=>{

 UserItem({
  user:item
 })

 },
 item=>item.id.toString()

)

key作用:

告诉框架:


这个组件

对应哪个数据对象

五十一、List刷新错误案例

假设:

聊天列表:


[
 {
 id:1,
 name:"Tom"
 },

 {
 id:2,
 name:"Jack"
 }
]

页面:


Tom
Jack

插入:


[
{
id:3,
name:"Lucy"
},

{
id:1,
name:"Tom"
},

{
id:2,
name:"Jack"
}
]

没有key:

框架可能:

认为:


第一个组件

还是Tom

结果:

组件状态错乱。


有key:


id=3

创建新组件


id=1

复用旧组件


id=2

复用旧组件

性能和准确性都更好。


五十二、二维数组监听

企业项目经常有:

例如:

商品分类:


[
 [
  商品1,
  商品2
 ],

 [
  商品3,
  商品4
 ]

]

模型:


@State
goods:Array<Array<Product>>

问题:

修改:


goods[0][0].price=99

是否刷新?

取决于:

Product。


正确:


@Observed
class Product{

 price:number=0

}

然后:

Item:


@ObjectLink
product:Product

完整监听链:


Array

↓

Array

↓

Product

↓

price

五十三、数组本身和数组元素是两回事

这是重点。

很多人混淆:


list.push()

和:


list[0].name=""

push

改变:

数组结构。

监听:

数组。


name修改

改变:

元素对象。

监听:

对象。


所以:

两个监听体系:

不同。


例如:


@State
users:Array<User>

负责:


增加

删除

排序

替换


@Observed
User

负责:


name

age

avatar

两者组合:

才能完整覆盖。


五十四、Map对象监听

例如:

缓存:


Map<number,User>

很多项目使用。

例如:


users.get(id)

修改:


users.get(1).name="Jack"

关键:

User必须:


@Observed

推荐:


@Observed
class User{

 name:string=""

}

Map:

负责:


key变化

User:

负责:


属性变化

五十五、Set监听

例如:

权限集合:


Set<string>

变化:


add()

delete()

需要:

状态容器感知。

但是:

Set内部对象:

仍然需要:

Observed。


例如:


Set<User>

那么:

User:

必须:


@Observed

五十六、为什么服务器返回JSON不能直接绑定?

这是企业项目非常常见问题。

服务器:


{
"name":"Tom",
"address":{
 "city":"Tokyo"
}
}

直接:


@State
user=response

然后:

修改:


user.name="Jack"

可能:

不刷新。


原因:

JSON解析出来:

只是:

普通对象。

不是:

Observed对象。


正确流程:


JSON

↓

DTO

↓

Model

↓

@Observed

↓

UI

例如:


class UserDTO{

 name:string

}

转换:


let user =
new UserModel(dto)

五十七、makeV1Observed作用

HarmonyOS 新版本:

状态管理V1:

针对:

已有对象。

提供:

转换能力。

概念:


普通对象

↓

makeV1Observed

↓

可观察对象

适合:

场景:

已有大型项目。

例如:

后端Model:


class User{

}

改动成本太高。

不方便:

全部添加:


@Observed

可以:

运行时增强。


五十八、V1 和 V2 状态管理区别

HarmonyOS 状态管理经历:

两个阶段。


V1

典型:


@State

@Prop

@Link

@ObjectLink

@Observed

特点:

显式声明。


V2

新的:


@ObservedV2

@Trace

体系。

特点:

更细粒度。

例如:


@ObservedV2
class User{

 @Trace
 name:string=""

}

只追踪:

指定字段。


区别:

V1 V2
监听粒度 对象级 属性级
学习成本 稍高
兼容性 新项目推荐
ObjectLink 需要 机制优化

五十九、什么时候使用 @Observed?

推荐:

以下情况:

✅ 页面共享对象

✅ 列表Item数据

✅ 复杂Model

✅ 局部刷新

✅ 多组件同步

不推荐:

❌ 网络DTO

❌ 临时对象

❌ 纯计算对象


六十、本篇总结

本篇核心:

1.

ArkUI监听的是:

数据访问关系

不是简单变量。


2.

@Observed负责:

对象属性变化通知。


3.

@ObjectLink负责:

组件订阅对象变化。


4.

数组监听和对象监听不同:


数组

负责结构变化


对象

负责属性变化

5.

企业项目推荐:


DTO

↓

Model(@Observed)

↓

Page(@State)

↓

Item(@ObjectLink)

↓

精准刷新

六十一、真实项目的数据流到底应该如何设计?

很多初学者的数据流通常是这样的:


网络请求
    │
    ▼
Page页面
    │
    ▼
List
    │
    ▼
Item

例如:


this.userList = result.data

随后:


this.userList[0].name = "Tom"

页面开始出现:

  • 有的刷新
  • 有的不刷新
  • List全部刷新
  • Item重复创建

于是不断:


this.userList = [...this.userList]

或者:


this.userList = JSON.parse(JSON.stringify(this.userList))

很多项目甚至整个工程都是这样刷新。

实际上这是 React/Vue 的开发习惯,不是 ArkUI 推荐方式。


六十二、HarmonyOS 推荐的数据流

官方推荐的数据流应该是:


Repository
      │
      ▼
Observed Model
      │
      ▼
Page(@State)
      │
      ▼
Item(@ObjectLink)
      │
      ▼
ArkUI Render

举个例子。

网络返回:


{
    "id":1,
    "name":"Tom",
    "age":18,
    "avatar":"..."
}

不要直接:


this.user = response

推荐:


let model = new User()

model.id = dto.id
model.name = dto.name
model.age = dto.age

this.user = model

以后所有修改:

全部操作:


this.user

不要再操作DTO。


六十三、为什么要把 DTO 和 Model 分开?

这是很多大型公司的规范。

例如:

服务器:


{
   "nickname":"Tom",
   "vip_level":5,
   "head_image":"xxx.png"
}

但是页面:

需要:


头像

昵称

VIP等级

如果直接使用:


head_image

整个项目:

都是:


user.head_image

以后服务器:

改成:


avatar

整个项目:

全部修改。

所以:

企业一般都会:


DTO

↓

Model

↓

UI

例如:

DTO:


interface UserDTO{

    head_image:string

}

Model:


@Observed
class User{

    avatar:string=""

}

转换:


user.avatar = dto.head_image

以后:

服务器怎么变。

UI不用改。


六十四、为什么大型项目都喜欢 ViewModel?

如果大家做过:

Android、

Flutter、

Compose、

SwiftUI

都会发现:

都有:


ViewModel

HarmonyOS同样推荐。

例如:


Repository

↓

UserViewModel

↓

User(@Observed)

↓

Page

↓

Component

ViewModel负责:

  • 请求网络
  • 转换数据
  • 更新Model
  • 通知页面

例如:


class UserViewModel{

    user:User = new User()

}

收到网络:


this.user.name = dto.name

页面:

自动刷新。


六十五、ObjectLink 为什么适合聊天软件?

例如:

微信:

聊天:

500条。

每条:


头像

昵称

最后消息

未读数量

时间

如果:

收到:

Tom:

新消息。

真正变化:

只有:


最后消息

未读数量

ObjectLink:

刷新:


Tom这一行

其它:

499条:

不会刷新。

这就是:

局部刷新。


六十六、如果不用 ObjectLink 会怎样?

很多项目:

这样写:


ForEach(
    this.list,
    item=>{
        Text(item.name)
    }
)

收到消息:


this.list[10].lastMessage="Hello"

为了刷新:

很多人:


this.list=[...this.list]

结果:


500个Item

全部重新Build

如果:

每个Item:

还有:


Image

RichText

Badge

动画

CPU:

立刻升高。


ObjectLink:

流程:


修改:

Conversation

↓

通知:

ObjectLink

↓

刷新:

ConversationItem

↓

结束

真正:

只刷新:

一个。


六十七、购物车为什么推荐 ObjectLink?

购物车:

例如:


100件商品

点击:


+

只是:


count++

普通刷新:


整个购物车

↓

重新Build

100件商品

ObjectLink:


Product

↓

count变化

↓

当前Item刷新

效率:

差距非常明显。


六十八、ObjectLink 能不能替代 EventHub?

很多新人:

喜欢:


A页面

↓

EventHub.emit()

↓

B页面监听

其实:

很多情况:

完全不用。

例如:

共享:

User对象。


Mine

↓

User

↑

Home

↑

Message

三个页面:

ObjectLink。

修改:


user.avatar="xxx"

三个页面:

自动同步。

无需:


emit()

也无需:


on()

代码:

少很多。


六十九、ObjectLink 能不能跨页面?

这里:

很多人理解错。

ObjectLink:

本身:

不能跨页面。

它只是:

连接:

同一个对象引用。

真正:

跨页面:

依赖:

共享对象。

例如:


AppStorage

↓

UserStore

↓

User

↓

PageA

↓

PageB

两个页面:

拿到:

同一个:


User

ObjectLink:

自然同步。


七十、企业推荐 Store 设计

例如:

建立:


store

   |

   UserStore.ets

   |

   OrderStore.ets

   |

   MessageStore.ets

UserStore:


export class UserStore{

    user:User=new User()

}

全局:

只有:

一个:


User

所有页面:

ObjectLink。

整个App:

数据一致。


七十一、ObjectLink 最大优势:组件自治

以前:

父组件:

需要:


updateChild()

或者:


callback()

现在:

Item:

自己监听。

例如:


@Component
struct UserItem{

    @ObjectLink
    user:User

}

父组件:

完全:

不用关心。

真正:

组件自治。


七十二、一个企业级错误案例

例如:

订单页面。

很多人:

点击:


确认收货

后台返回:


status=已完成

代码:


this.orders[index]=newOrder

整个List:

刷新。

实际上:

推荐:


this.orders[index].status="已完成"

Item:

ObjectLink:

自己刷新。

List:

不刷新。


七十三、性能测试对比

假设:

列表:

1000条。

每个Item:


头像

昵称

时间

消息

动画

修改:

一个Item。

方案一:


this.list=[...this.list]

结果:


1000

build

方案二:


item.name="Tom"

ObjectLink:


1

build

CPU:

相差:

几十倍。

这也是为什么 HarmonyOS 官方一直推荐对象级响应式,而不是频繁替换整个列表。


七十四、什么时候不要使用 @ObjectLink?

虽然它很好。

但是:

下面这些情况:

不推荐:

① 普通数字


@State
count:number

不用:


@ObjectLink

② 字符串


title:string

不用。


③ 临时变量

例如:


let index=0

不用。


④ 网络DTO

服务器返回:


{
"id":1
}

不要:

直接:

ObjectLink。


真正:

推荐:


Model

七十五、本章总结

企业项目:

最佳实践:


Network

↓

DTO

↓

Repository

↓

ViewModel

↓

Observed Model

↓

State(Page)

↓

ObjectLink(Item)

↓

Render

整个刷新:

始终:

围绕:

Model对象。

而不是:

数组。

不是:

JSON。

不是:

DTO。

更不是:

重新赋值。

七十六、先理解 ArkUI 响应式框架整体架构

很多开发者只知道:


this.user.name = "Tom"

页面刷新了。

但是:

真正发生的是一整套响应式流程。

如下图:


                用户点击按钮
                      │
                      ▼
            this.user.name="Tom"
                      │
                      ▼
          @Observed Setter 被触发
                      │
                      ▼
         ObservableObject 通知变化
                      │
                      ▼
          SubscriberManager 查找订阅者
                      │
                      ▼
        找到所有 @ObjectLink Component
                      │
                      ▼
          标记组件 Dirty(MarkDirty)
                      │
                      ▼
             FrameScheduler 调度刷新
                      │
                      ▼
             Component Build()
                      │
                      ▼
              ArkUI Diff 比较
                      │
                      ▼
             RenderNode 更新
                      │
                      ▼
                  GPU 渲染

很多文章讲到:


Setter
↓

刷新

其实:

中间还有很多步骤。


七十七、@Observed 底层设计思想

虽然官方没有公开完整源码,但根据 ArkUI 的工作机制,可以理解其设计思想。

普通对象:


class User {
  name: string = ""
}

实际上只是:


对象

没有任何通知能力。

而:


@Observed
class User {
  name: string = ""
}

可以理解为:


ObservableObject

        │

        ├──── 属性1

        ├──── 属性2

        ├──── 属性3

        │

SubscriberManager

也就是说:

每一个:


Setter

都会:

通知:


SubscriberManager

七十八、SubscriberManager 是什么?

可以理解为:

整个 ArkUI 的:


观察者中心

例如:


User对象

↓

SubscriberManager

↓

Child1

↓

Child2

↓

Child3

↓

Child4

只要:

User:

变化。

SubscriberManager:

立即:

通知:


Child1

Child2

Child3

Child4

进行刷新。


七十九、为什么不用事件通知?

有人问:

为什么不用:


EventHub.emit()

或者:


Emitter.emit()

原因:

事件:

属于:


广播

例如:


A页面

↓

emit()

↓

所有监听者收到

但是:

ObjectLink:

属于:


对象依赖

例如:


User

↓

Avatar组件

↓

Name组件

↓

Vip组件

修改:


User.avatar

只刷新:

依赖:

头像的组件。

而不是:

整个页面。


八十、MarkDirty 是什么?

HarmonyOS 渲染框架:

不会:

立即:

重新 build。

例如:


this.user.name = "Tom"

this.user.age = 18

this.user.avatar = "xxx"

如果:

每次:

都:

刷新。

那么:


Build()

Build()

Build()

性能:

很差。

实际上:

ArkUI:

先:


MarkDirty

例如:


UserItem

↓

Dirty = true

等:

这一帧:

统一:

刷新。

这就是:

批量更新。


八十一、Diff 为什么重要?

假设:

页面:


Column() {

    Avatar()

    Name()

    Vip()

}

修改:


user.name = "Jack"

并不是:

整个:


Column

重建。

而是:

Diff:

发现:


Avatar

没变

↓

Vip

没变

↓

Name

变了

最终:

更新:


Text

不是:

整个页面。


八十二、为什么 ObjectLink 比 EventHub 性能高?

例如:

聊天:

1000条。

收到:

Tom:

消息。

EventHub:

流程:


emit()

↓

页面收到

↓

刷新List

↓

1000 Item

ObjectLink:

流程:


Conversation

↓

Subscriber

↓

ChatItem

↓

1个刷新

差距:

非常明显。


八十三、HarmonyOS 为什么没有像 Vue 那样全部 Proxy?

Vue:

整个:


Reactive()

↓

全部Proxy

HarmonyOS:

采用:


按需观察

例如:

只有:


@Observed

才:

建立:

观察能力。

原因:

大型项目:

对象:

可能:

几十万个。

全部Proxy:

初始化:

成本:

太高。

所以:

HarmonyOS:

采用:

显式声明。


八十四、HarmonyOS 5 状态管理 V2

HarmonyOS 5 开始:

推荐:

新的状态管理。

例如:


@ObservedV2
class User {

    @Trace
    name:string=""

}

以前:

V1:

整个对象:

通知。

现在:

V2:

可以:

指定:


Trace字段

例如:


name

↓

刷新

而:


cache

log

temp

不用:

监听。

粒度:

更细。


八十五、企业开发最佳实践

推荐:

项目目录:


model
│
├── User.ets
├── Message.ets
├── Order.ets
└── Product.ets

store
│
├── UserStore.ets
├── MessageStore.ets
└── OrderStore.ets

viewmodel
│
├── UserViewModel.ets
├── ChatViewModel.ets
└── OrderViewModel.ets

pages
│
├── HomePage
├── MinePage
└── OrderPage

components
│
├── UserItem
├── ChatItem
└── ProductItem

数据流:


Network

↓

Repository

↓

ViewModel

↓

Store

↓

Observed Model

↓

Page

↓

ObjectLink Component

这样:

整个项目:

职责清晰。


八十六、开发中最容易踩的 10 个坑

问题 原因 正确做法
修改对象属性不刷新 没有 @Observed 给 Model 添加 @Observed
子组件不刷新 没有 @ObjectLink 使用 @ObjectLink 接收对象
嵌套对象不刷新 内层不是 @Observed 每层需要可观察对象
JSON 不刷新 普通对象 转换为 Model
List 全部刷新 替换整个数组 修改对象属性
ForEach 错乱 没有 key 使用稳定唯一 id
ObjectLink 无效 复制对象 保持对象引用
DTO 与 UI 混用 数据职责混乱 DTO → Model 转换
Store 多实例 页面各自 new 使用共享 Store
滥用 EventHub 所有页面广播 优先对象响应式

八十七、HarmonyOS 面试高频题(30 题精选)

1、@Observed 的作用是什么?

答案:

使普通 Class 成为可观察对象,当属性发生变化时,可以向订阅它的组件发送通知。


2、@ObjectLink 的作用是什么?

答案:

建立组件与 @Observed 对象之间的双向引用关系,使对象属性变化能够驱动组件刷新。


3、为什么 @Observed 不能单独使用?

答案:

@Observed 只负责发送对象变化通知,没有组件订阅这些通知时,页面不会刷新,需要 @ObjectLink 建立观察关系。


4、@ObjectLink 和 @Link 的区别?

答案:

@Link @ObjectLink
基本类型/状态共享 可观察对象共享
面向变量 面向对象
不负责对象属性监听 支持对象属性监听

5、为什么修改 this.user.name 有时不刷新?

可能原因:

  • User 未添加 @Observed
  • 子组件未使用 @ObjectLink
  • 使用普通 JSON 对象
  • 中间嵌套对象未添加 @Observed

6、为什么 ForEach 一定要提供 key?

因为 ArkUI Diff 算法依赖稳定的 key 来复用组件,否则插入、删除或排序时容易出现组件状态错乱,并增加重建成本。


7、为什么推荐 Class 而不是 Object?

Class 可以:

  • @Observed 修饰
  • 封装业务逻辑
  • 统一初始化
  • 提供计算属性和方法

更适合企业级项目。


8、什么时候适合使用 @ObjectLink?

适用于:

  • 聊天消息 Item
  • 通讯录 Item
  • 商品 Item
  • 订单 Item
  • 用户信息卡片
  • 多组件共享同一对象

9、HarmonyOS V1 和 V2 状态管理有什么区别?

V1 以 @Observed + @ObjectLink 为核心。

V2 引入了更细粒度的状态追踪(如 @ObservedV2@Trace),减少不必要的通知,更适合新项目。


10、ObjectLink 会复制对象吗?

不会。

它保存的是同一个对象引用,父子组件共同操作同一个实例,因此修改任意一方都会保持一致。


全文总结

经过整套系列,我们已经建立了完整的知识体系:


普通 Class
        │
        ▼
@Observed
        │
        ▼
可观察对象(Observable)
        │
        ▼
@State 持有对象
        │
        ▼
父组件传递引用
        │
        ▼
@ObjectLink 建立订阅
        │
        ▼
属性变化通知
        │
        ▼
SubscriberManager
        │
        ▼
MarkDirty
        │
        ▼
Build
        │
        ▼
Diff
        │
        ▼
Render

企业项目最佳实践

建议遵循以下原则:

  1. DTO 与 UI Model 分离,不要直接把网络返回 JSON 绑定到 UI。
  2. 所有需要响应式更新的业务模型使用 @Observed
  3. 列表 Item 使用 @ObjectLink,避免整个列表重建。
  4. 嵌套对象建立完整观察链,每层需要可观察能力。
  5. 使用稳定的 ForEach key,保证 Diff 正确复用组件。
  6. 优先对象响应式,而不是频繁替换整个数组或使用事件广播
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐