适用版本:

HarmonyOS NEXT

API12~API18+

ArkTS

ArkUI状态管理

前言

HarmonyOS 的状态管理体系相比 Vue、React、Flutter 有很大的不同。

很多开发者刚开始学习 ArkUI 时,经常都会遇到下面几个问题:


为什么对象属性修改以后UI没有刷新?

为什么数组里面对象修改了也没有刷新?

为什么class里面嵌套class无法更新?

为什么用了@State还是没反应?

@Observed到底什么时候需要?

@ObjectLink到底是干嘛的?

为什么官方一直强调ObjectLink要和Observed一起使用?

如果只是开发简单页面,上面的坑可能很少遇到。

但是到了真正的企业项目,例如:

  • 电商APP
  • IM聊天
  • OA办公
  • CRM系统
  • 医疗系统
  • 金融APP

数据模型几乎全部都是:


User
 ├── UserInfo
 │      ├── Address
 │      ├── Company
 │      └── Role
 │
 ├── OrderList
 │      ├── Goods
 │      ├── Coupon
 │      └── Logistics
 │
 └── MessageList

全部都是多层嵌套对象。

这时候,如果不了解 @Observed + @ObjectLink 的工作机制,页面几乎一定不会刷新。

本文将彻底讲透 HarmonyOS 多层对象渲染机制,包括:

  • @Observed 原理
  • @ObjectLink 原理
  • 为什么只能监听第一层
  • 多层对象为什么失效
  • ObjectLink为什么必须放子组件
  • 多层ObjectLink如何传递
  • 数组嵌套对象
  • 对象嵌套对象
  • 二维数组
  • 企业项目最佳实践
  • 常见坑总结

看完以后,你基本可以彻底理解 HarmonyOS 的深层状态管理。官方文档和社区资料也强调:@Observed 用于使类实例可观察,而 @ObjectLink 在子组件中与这些实例建立双向绑定,从而支持嵌套对象的更新。


目录


1. 为什么普通对象不会刷新
2. ArkUI状态管理回顾
3. @State监听对象原理
4. 为什么只能监听第一层
5. @Observed介绍
6. @Observed工作机制
7. @ObjectLink介绍
8. ObjectLink双向绑定原理
9. 多层对象刷新原理
10. ObjectLink为什么必须放子组件
11. 多层嵌套对象实战
12. 多层数组实战
13. 二维数组实战
14. 企业项目Model设计
15. ObjectLink与Prop区别
16. ObjectLink与Link区别
17. ObjectLink与Provide区别
18. ObjectLink性能分析
19. 常见踩坑
20. 最佳实践总结

第一章 为什么普通对象不会刷新

很多人的第一个例子都是这样。


@State
user: User = new User()

然后:


this.user.name = "Tom"

页面刷新了。

于是很多人认为:

HarmonyOS 能监听对象属性。

其实,这是一个容易产生误解的地方。

真正被监听的,并不是所有对象层级,而是状态管理系统能够追踪到的那一层对象变化。对于更深层的嵌套对象,仅靠 @State 往往无法自动触发刷新,需要借助 @Observed@ObjectLink 等机制来建立可观察关系。


看一个例子


class User{

    name:string="Tom"

    age:number=18

}

页面


@State
user:User=new User()

按钮


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

    this.user.name="Jack"

})

很多情况下页面会更新。

于是大家误认为:


Harmony监听了name

实际上并不是。


第二章 为什么第二层对象失效

例如:


class Address{

    city:string="北京"

}

class User{

    address:Address=new Address()

}

页面:


Text(this.user.address.city)

修改:


this.user.address.city="上海"

很多人发现:


页面没刷新!!

为什么?

因为:


第一层:
User

第二层:
Address

第三层:
city

State 默认只能可靠地处理它直接管理的对象层级,而深层嵌套对象并不会自动建立观察关系,因此修改 city 时,UI 不一定会收到通知。解决方案就是让嵌套类具备可观察能力,并在子组件中使用 @ObjectLink 接收这些对象。


第三章 ArkUI 的渲染机制

真正理解 ObjectLink。

必须理解 ArkUI Render Pipeline。

整个更新流程:


数据变化

↓

状态管理

↓

Dirty Check

↓

Build()

↓

Diff

↓

RenderNode

↓

GPU

其中:


State负责通知

ObjectLink负责引用

Observed负责观察对象

三者共同完成深层对象刷新。


第四章 @Observed 到底是什么

官方定义:

@Observed 修饰的 class,其实例属性变化可以被观察;它通常与 @ObjectLink 配合,用于嵌套对象或数组的双向数据同步。

例如:


@Observed
class User{

    name:string="Tom"

}

以后:


user.name="Jack"

这个变化就可以被 ArkUI 感知。

注意:

仅给外层类添加 @Observed 并不足以覆盖所有更深层嵌套。对于嵌套 class,每一层需要满足可观察条件(或在较新的 API 中使用对应的新机制),否则深层修改仍可能无法刷新。

第五章 深入理解 @Observed 底层原理

上一章我们知道了:

  • @State 负责管理组件状态
  • @Observed 负责让 Class 具有可观察能力
  • @ObjectLink 负责子组件共享对象引用

但是很多同学仍然会疑惑:


为什么加了一个 @Observed 就能刷新?

它到底监听了什么?

是不是用了 Proxy?

是不是类似 Vue3 的 reactive()?

是不是和 Flutter ChangeNotifier 一样?

这一章,我们从底层机制来分析 ArkUI 是如何实现对象监听的。


5.1 ArkUI 为什么需要 @Observed

先来看一个普通类。


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

创建对象:


let user = new User()

user.name = "Jack"

对于 ArkTS 而言,这只是一次普通的成员变量赋值。

CPU 完成的事情只有:


找到对象地址

↓

找到name成员

↓

写入Jack

↓

结束

整个过程中:

UI 根本不知道对象发生了变化。

所以:


数据改了

UI没收到通知

页面自然不会刷新

这也是所有声明式UI框架都会解决的问题。

例如:

Vue


reactive(user)

React


setState()

Flutter


notifyListeners()

HarmonyOS


@Observed

它们本质都是:

告诉框架:"这个对象发生变化以后,请通知UI重新渲染。"


5.2 没有 @Observed 会发生什么

例如:


class User {

  name: string = "Tom"

}

页面:


@State
user: User = new User()

按钮:


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

    this.user.name = "Jack"

})

很多情况下:


页面没有任何变化

为什么?

因为:


State保存的是:

user对象引用

真正变化的是:


user

↓

name成员

不是:


user对象地址

所以:

State 不知道里面发生了什么。


5.3 加入 @Observed 以后发生什么

现在:


@Observed
class User {

    name:string="Tom"

}

再创建对象:


let user=new User()

实际上:

ArkUI 不再把它当成普通对象。

而是:


User

↓

ObservedObject

↓

ArkUI Observer

↓

Render Scheduler

整个对象进入:

可观察状态。

以后:


user.name="Jack"

流程变成:


修改name

↓

Observed发现变化

↓

通知ObjectLink

↓

通知State

↓

标记Dirty

↓

Build()

↓

Diff

↓

刷新UI

相比普通对象:

多了一步:


通知框架

这就是 @Observed 的意义。


5.4 为什么官方要求修饰 Class

很多人这样写:


@Observed
let user = {

    name:"Tom"

}

这是错误的。

原因:

Observed 的作用对象必须是:


Class

例如:


@Observed
class User{

}

因为 ArkUI 会:


创建对象

↓

包装对象

↓

生成Observer

↓

记录成员变化

只有类实例才能建立稳定的对象生命周期和观察关系。

普通 Object:


{

}

生命周期无法保证。

因此:

官方只支持:


Class

5.5 一个对象对应一个 Observer

例如:


@Observed
class User{

    name="Tom"

}

创建:


let u1=new User()

let u2=new User()

实际上:


u1

↓

Observer1


u2

↓

Observer2

完全独立。

所以:


u1.name="Jack"

不会影响:


u2

因为:

它们拥有不同的观察器。

可以理解成:


User实例

↓

绑定Observer

↓

Observer只负责当前实例

5.6 Observer 内部工作流程(示意)

虽然 ArkUI 官方没有公开完整源码,但根据公开文档、ArkUI 状态管理行为以及实际运行特性,可以将其工作流程抽象为如下模型:


@Observed

↓

创建对象

↓

生成观察器(Observer)

↓

记录成员列表

↓

等待属性修改

↓

属性变化

↓

通知依赖

↓

刷新UI

也就是说:

真正监听的不是:


整个页面

而是:


对象成员

例如:


user.name="Tom"

Observer 会收到:


name发生变化

随后:


通知依赖它的组件

这样:

只有相关组件重新 Build。

而不是:

整个页面刷新。


5.7 为什么性能比全局刷新高

例如页面:


首页

↓

用户信息

↓

订单

↓

广告

↓

推荐商品

↓

购物车

修改:


user.name="Jack"

真正刷新的只有:


用户信息组件

不会:


购物车刷新

广告刷新

订单刷新

因为:

Observer 知道:

谁依赖了:


user

这就是:

声明式UI最大的优势。


5.8 多个属性如何监听

例如:


@Observed
class User{

    name="Tom"

    age=18

    phone="123"

    city="北京"

}

修改:


user.city="上海"

Observer:

记录的是:


name

age

phone

city

修改:


city

通知:


city对应依赖

如果页面:


Text(user.name)

理论上:

city 无关的 UI 可以避免不必要的更新(具体优化粒度仍取决于 ArkUI 当前版本的实现)。

这就是:

依赖收集。


5.9 为什么不能监听 private

例如:


@Observed
class User{

    private token="abc"

}

一般情况下:

UI:


Text(user.token)

无法访问。

既然:

UI 不依赖:


token

Observer:

也不会建立:


token

↓

UI

所以:

private 成员通常不会作为 UI 响应式数据来源使用。


5.10 一个完整的数据流

整个过程可以总结为:


User对象

↓

@Observed

↓

Observer

↓

ObjectLink

↓

State

↓

Dirty

↓

Build()

↓

Diff

↓

RenderNode

↓

GPU

↓

页面刷新

至此,我们已经理解了:

  • 为什么普通对象不会刷新
  • 为什么加了 @Observed 就具备可观察能力
  • 为什么每个实例都有独立的观察关系
  • 为什么只更新依赖它的组件而不是整个页面

第六章 为什么还需要 @ObjectLink?

很多开发者学到这里都会产生新的疑问:


既然 @Observed 已经能监听对象变化,

为什么官方还要设计 @ObjectLink?

是不是有 @Observed 就够了?

答案是:

不够。

因为:

@Observed 解决的是:

对象可观察。

而:

@ObjectLink 解决的是:

对象如何跨组件共享,并且保持同一个引用。

第六章 为什么还需要 @ObjectLink?

上一章,我们已经知道:

@Observed 可以让对象具有可观察能力。

很多开发者就会产生一个疑问:


既然对象已经能监听变化了,

为什么还需要 @ObjectLink?

不用 ObjectLink 行不行?

答案是:

绝大多数情况下,不行。

因为:

@Observed 负责"观察对象"。

而:

@ObjectLink 负责"共享同一个对象"。

这是两个完全不同的概念。


6.1 先理解组件之间的数据传递

假设有两个组件:


HomePage

↓

UserCard

父组件:


@Entry
@Component
struct HomePage {

    @State
    user: User = new User()

    build() {

        Column() {

            UserCard({
                user: this.user
            })

        }

    }

}

子组件:


@Component
struct UserCard {

    @Prop
    user: User

    build() {

        Text(this.user.name)

    }

}

很多人认为:


父组件user

↓

子组件user

↓

就是同一个对象

其实不是。


6.2 @Prop 到底干了什么?

很多同学误认为:


Prop

=

引用传递

实际上:

Prop 更偏向于"单向数据输入"。

它的设计目的:


父组件

↓

子组件

而不是:


父组件

⇄

子组件

所以:

子组件:


this.user.name="Jack"

并不是官方推荐的数据流。

官方推荐:


父组件修改

↓

通知子组件刷新

而不是:


子组件直接改父组件

因此:

Prop 更像:

React 的:


Props

Vue:


Props

6.3 ObjectLink 解决什么问题?

现在:


父组件

↓

订单

↓

商品

↓

价格

例如:


@Observed
class Goods{

    title:string="Mate70"

}

父组件:


@State
goods:Goods=new Goods()

子组件:


@ObjectLink
goods:Goods

注意:

这里:

不是:


复制Goods

而是:


引用Goods

因此:

父组件:


this.goods.title="Mate X7"

子组件:

立即刷新。

反过来:

子组件:


this.goods.title="Mate XT"

父组件:

也立即刷新。

因为:


只有一个Goods对象

6.4 ObjectLink 为什么叫 Link

名字已经说明了一切。

不是:


Copy

而是:


Link

即:


链接

引用

共享

整个结构:


Home

↓

Goods对象

↑

↓

GoodsCard

两个组件:

共享:


同一块内存

因此:

任何地方修改:

都是:


同一个对象

6.5 内存示意图

没有 ObjectLink:


父组件

userA

↓

Tom

子组件:


userB

↓

Tom

实际上:

两个对象。

修改:


userA.name

不会影响:


userB

而:

ObjectLink:


父组件

↓

User对象

↑

↓

子组件

两个变量:

都指向:


同一个地址

例如:


0x10086

所以:

修改:


user.name="Jack"

所有组件:

立即更新。


6.6 为什么官方要求配合 @Observed

这是很多人的疑问。

例如:


class User{

    name="Tom"

}

然后:


@ObjectLink
user:User

发现:


不能刷新

为什么?

因为:

ObjectLink:

只是:


共享引用

并不会:


监听变化

真正监听的是:


@Observed

所以:

官方一直强调:


Observed

+

ObjectLink

缺一不可。

可以理解:


Observed

负责报警器

ObjectLink:


负责电话线

报警器:

发现变化。

电话线:

通知所有组件。

少任何一个:

消息都传递不了。


6.7 ObjectLink 底层工作流程

整个流程:


Parent

↓

State

↓

ObservedObject

↓

ObjectLink

↓

Child

修改:


goods.title="手机"

流程:


修改title

↓

Observed发现

↓

通知ObjectLink

↓

ObjectLink通知组件

↓

组件Dirty

↓

Build()

↓

Diff

↓

刷新

整个过程:

没有:


复制对象

只有:


共享引用

6.8 为什么性能更高

如果不用 ObjectLink。

父组件:

修改:


goods.title

需要:


重新创建对象

↓

重新传递

↓

重新初始化子组件

而:

ObjectLink:

只需要:


修改字段

例如:


title

↓

Mate70

↓

MateXT

对象:

没有重新创建。

因此:

性能非常高。

尤其:

商城:


500个商品

聊天:


2000条消息

对象:

都不会重新复制。


6.9 一个企业级例子

例如:

订单:


@Observed
class Order{

    orderNo=""

    user:User=new User()

}

用户:


@Observed
class User{

    name=""

}

页面:


订单页面

↓

订单组件

↓

用户组件

订单组件:


@ObjectLink
order:Order

用户组件:


@ObjectLink
user:User

修改:


user.name="张三"

刷新:


用户组件

√

订单组件:

如果依赖:


order.user.name

也刷新。

整个订单:

不用重新创建。


6.10 ObjectLink 最大的优势

一句话总结:

ObjectLink 不是用来传数据,而是用来传"对象引用"。

因此:

它最适合:

  • 用户信息
  • 商品信息
  • 聊天消息
  • 地址对象
  • 配送信息
  • 评论对象
  • 树形数据
  • 多层嵌套 Model

这些复杂对象。


6.11 @ObjectLink 与 @Link 的区别

很多开发者第一次接触时,经常把这两个装饰器混淆。

对比项 @Link @ObjectLink
作用对象 基本类型、普通状态 @Observed 对象
是否共享对象引用 不适用(绑定状态变量)
是否适合复杂 Model
是否用于多层嵌套对象
是否依赖 @Observed

可以简单理解:

  • @Link:共享的是状态值(或状态变量)。
  • @ObjectLink:共享的是对象实例

6.12 企业项目为什么大量使用 @ObjectLink

在实际项目中,一个页面通常不是一个组件,而是由几十甚至上百个子组件组成。

例如商城商品详情页:


GoodsDetailPage
│
├── BannerComponent
├── GoodsInfoComponent
├── CouponComponent
├── SpecComponent
├── CommentComponent
├── RecommendComponent
└── BottomBarComponent

它们很多都需要访问同一个 Goods 对象。

如果每个组件都重新复制数据:

  • 内存占用增加
  • 状态容易不一致
  • 更新链路复杂

而使用 @Observed + @ObjectLink 后:


Goods(唯一实例)
        │
 ┌──────┼────────┬─────────┐
 │      │        │         │
Banner Info  Coupon  Comment

所有组件共享同一个对象,只要对象发生变化,依赖它的组件即可同步更新,代码也更容易维护。


本章总结

这一章重点理解了几个核心概念:

  • @Observed 负责让对象变成可观察对象
  • @ObjectLink 负责在父子组件之间共享同一个对象实例
  • 两者通常配合使用,才能实现复杂对象的响应式更新。
  • @ObjectLink 更适合企业级项目中的多层对象、列表对象和大型数据模型。

第七章 为什么 @ObjectLink 必须用于子组件?

很多开发者第一次学习 @ObjectLink 时都会有这样的疑问:


既然 ObjectLink 可以共享对象,

那为什么不能直接在父组件使用?

为什么官方示例都是放在子组件?

它到底限制了什么?

其实,这涉及到 ArkUI 状态管理架构设计

很多文章只告诉你:

ObjectLink 只能用于子组件。

却没有解释:

为什么只能这样设计?

这一章,我们从 组件树(Component Tree) 的角度彻底讲透。


7.1 先理解 ArkUI 的组件树

任何一个 ArkUI 页面,最终都会变成一棵组件树。

例如:


Entry

↓

HomePage

├────Header

├────UserInfo

├────Order

├────Goods

└────Footer

实际上:

每一个组件:

都有自己的:


State

Build()

生命周期

RenderNode

也就是说:


HomePage

不是

UserInfo

而是:

两个完全独立的组件实例。


7.2 数据如何向下流动

例如:


@Entry
@Component
struct HomePage {

    @State
    user: User = new User()

    build() {

        Column() {

            UserCard({
                user: this.user
            })

        }

    }

}

这里发生了什么?

其实:


HomePage

↓

创建User对象

↓

创建UserCard

↓

把user引用传过去

注意:

这里:

不是:


复制User

而是:


引用User

因此:

父组件:

拥有:


真正的数据

子组件:

拥有:


数据入口

这就是:

ObjectLink 的意义。


7.3 ObjectLink 为什么不能自己创建对象?

很多人喜欢这样写:


@Component
struct UserCard {

    @ObjectLink
    user: User = new User()

}

这是错误的。

为什么?

因为:

ObjectLink:

不是:


拥有对象

而是:


引用对象

如果:

自己:


new User()

那么:


对象来源:

自己

就没有:


父组件

整个:


引用链

断掉了。

因此:

ObjectLink:

必须:

来自:


父组件

而不是:

自己创建。


7.4 正确的数据流

例如:

父组件:


@State
user = new User()

子组件:


@ObjectLink
user: User

整个关系:


Parent

↓

State

↓

User对象

↑

↓

ObjectLink

↓

Child

只有:

一个:


User

因此:

任何地方修改:

都是:


同一块内存

7.5 为什么父组件不能使用 ObjectLink

很多同学会问:


HomePage

里面

为什么不能:

@ObjectLink

原因非常简单:

ObjectLink:

表示:


引用别人

父组件:

没有:


父亲

它自己:

就是:


Owner(拥有者)

因此:

必须:


State

而不是:


ObjectLink

可以理解:


State

=

拥有数据

ObjectLink:


=

借用数据

所以:

父组件:

一定:

拥有数据。

子组件:

一定:

借数据。


7.6 一个生活中的例子

假设:

办公室:

有一台:


打印机

老板:

拥有:

打印机。

员工:

使用:

打印机。

关系:


老板

↓

打印机

↑

↓

员工A

↓

员工B

↓

员工C

老板:

对应:


State

员工:

对应:


ObjectLink

员工:

不能:

自己:


重新买打印机

否则:

别人:

看不到。

ObjectLink:

也是一样。


7.7 多个子组件共享一个对象

例如:


Home

├──UserCard

├──UserAvatar

├──UserInfo

└──UserDetail

父组件:


@State
user = new User()

四个子组件:

全部:


@ObjectLink
user

整个结构:


          User

      ↑ ↑ ↑ ↑

 Card Avatar Info Detail

修改:


user.name="Tom"

所有组件:

一起:

刷新。

因为:

引用:

同一个对象。


7.8 ObjectLink 可以无限传递吗?

答案:

可以。

例如:


Home

↓

Order

↓

Goods

↓

Comment

↓

Reply

每一级:

都是:


@ObjectLink

例如:

第一层:


OrderComponent{

    @ObjectLink
    order:Order

}

第二层:


GoodsComponent{

    @ObjectLink
    goods:Goods

}

第三层:


CommentComponent{

    @ObjectLink
    comment:Comment

}

形成:


Order

↓

Goods

↓

Comment

↓

Reply

引用链:

一直:

保持。

因此:

第五层:

修改:


reply.content="你好"

第一层:

如果:

依赖:


reply.content

同样:

刷新。


7.9 ObjectLink 是不是越多越好?

当然不是。

很多新手:

喜欢:

整个页面:

全部:

ObjectLink。

例如:


Home

↓

100个组件

↓

全部ObjectLink

这是:

错误设计。

因为:

ObjectLink:

意味着:


共享对象

共享:

越多。

依赖:

越复杂。

以后:

维护:

越来越困难。

企业项目:

通常:


页面

↓

模块

↓

模块拥有数据

↓

子组件ObjectLink

而不是:

整个页面:

全部:

共享。


7.10 企业项目推荐结构

例如:

商城:


GoodsPage

↓

GoodsViewModel

里面:


Goods

Coupon

Spec

Comment

Address

页面:


GoodsPage

↓

GoodsModule

↓

CommentModule

↓

CouponModule

每个模块:

维护:

自己的:

ObjectLink。

而不是:

整个:

页面:

全部:

共享一个:

巨大:

Model。

这也是:

MVVM:

推荐:

设计。


7.11 一个容易踩坑的案例

例如:

父组件:


@State
user = new User()

子组件:


@ObjectLink
user: User

然后:

在子组件中:


this.user = new User()

很多人觉得:


只是重新赋值

实际上:

这一句:

等于:


切断引用关系

原来:


Parent

↓

UserA

↑

↓

Child

变成:


Parent

↓

UserA

Child

↓

UserB

以后:

父组件:

修改:


UserA.name

子组件:

不会刷新。

子组件:

修改:


UserB.name

父组件:

也不会刷新。

这是企业开发中最常见的问题之一。

正确做法:

不要给 @ObjectLink 修饰的对象重新赋值为新的实例,而是修改对象内部的属性:


this.user.name = 'Jack'
this.user.age = 20

而不是:


// ❌ 不推荐
this.user = new User()

7.12 本章总结

这一章,我们彻底理解了 @ObjectLink 的定位:

@State:拥有数据(Owner)
@ObjectLink:引用数据(Reference)
✅ 父组件负责创建对象
✅ 子组件负责共享对象
✅ 多个子组件可以共享同一个对象
✅ 不要给 @ObjectLink 重新赋值新的实例
✅ 按模块划分共享范围,避免整个页面滥用 @ObjectLink

第八章 三层、四层、五层嵌套对象如何实现自动刷新(企业项目核心)

前面我们已经了解:

  • @Observed 负责对象可观察
  • @ObjectLink 负责共享对象引用
  • 父组件拥有对象
  • 子组件引用对象

但是到了真正的企业项目,大多数开发者都会遇到下面的问题:


User
 │
 ├── Company
 │      │
 │      ├── Department
 │      │        │
 │      │        ├── Employee
 │      │        │       │
 │      │        │       └── Address
 │      │
 │      └── Leader
 │
 └── OrderList

修改:


employee.address.city = "深圳"

结果:


UI没有刷新!!

为什么?

因为真正困难的,不是一层对象。

而是:

对象里面还有对象。

这也是 HarmonyOS 企业开发最容易踩坑的地方。


8.1 一层对象为什么容易刷新?

例如:


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

页面:


@State
user: User = new User()

修改:


this.user.name = "Jack"

刷新:


没有问题。

因为:


User

↓

name

只有:

一层。


8.2 两层对象开始出现问题

例如:


class Address {

    city = "北京"

}

@Observed
class User {

    address = new Address()

}

页面:


Text(this.user.address.city)

按钮:


this.user.address.city = "上海"

很多人发现:


页面没刷新

为什么?

画一下结构:


User

↓

Address

↓

city

真正变化的是:


Address.city

而:

ArkUI 当前观察到的是:


User

里面:


address引用

并不是:


city

所以:

没有通知。


8.3 正确做法:每一层对象都需要具备可观察能力

企业项目中,一个常见原则是:

只要某一层对象需要独立响应属性变化,就应让这一层成为可观察对象。

例如:


@Observed
class Address {

    city = "北京"

}

@Observed
class User {

    address = new Address()

}

此时:


User

↓

Address

↓

city

两层:

都能够:

建立观察关系。

这也是官方推荐在嵌套模型中使用 @Observed 的原因之一。


8.4 三层对象

例如:


@Observed
class City {

    name = "北京"

}

@Observed
class Address {

    city = new City()

}

@Observed
class User {

    address = new Address()

}

整个结构:


User

↓

Address

↓

City

↓

name

修改:


user.address.city.name = "上海"

只有:

当:


City

Address

User

整个引用链:

保持可观察。

UI:

才能:

稳定刷新。


8.5 企业项目真实模型

例如:

商城:


User

↓

Order

↓

Goods

↓

Spec

↓

SKU

↓

Price

代码:


@Observed
class Price{

    value:number=6999

}

@Observed
class SKU{

    price:Price=new Price()

}

@Observed
class Spec{

    sku:SKU=new SKU()

}

@Observed
class Goods{

    spec:Spec=new Spec()

}

@Observed
class Order{

    goods:Goods=new Goods()

}

整个:

形成:


Order

↓

Goods

↓

Spec

↓

SKU

↓

Price

修改:


order.goods.spec.sku.price.value = 7999

整个:

引用:

都是:

Observed。

因此:

ObjectLink:

才能:

通知。


8.6 多层 ObjectLink 如何传递

例如:

页面:


OrderPage

↓

OrderComponent

↓

GoodsComponent

↓

SpecComponent

↓

PriceComponent

第一页:


@State
order = new Order()

传递:


OrderComponent({
    order:this.order
})

Order:


@ObjectLink
order:Order

继续:


GoodsComponent({
    goods:this.order.goods
})

Goods:


@ObjectLink
goods:Goods

继续:


SpecComponent({

    spec:this.goods.spec

})

Spec:


@ObjectLink
spec:Spec

最后:


PriceComponent({

    price:this.spec.sku.price

})

Price:


@ObjectLink
price:Price

整个:

形成:


Order

↓

Goods

↓

Spec

↓

SKU

↓

Price

引用:

一直:

保持。

没有:

复制。


8.7 为什么不要一直传整个 User

很多新人喜欢:


Home

↓

User

↓

User

↓

User

↓

User

↓

User

整个:

页面:

全部:


@ObjectLink
user

然后:

第五层:

自己:


user.company.department.employee.address.city

虽然:

可以。

但是:

非常差。

原因:

依赖:

越来越大。

例如:

Address:

修改。

结果:

整个:

User:

依赖:

全部:

刷新。

企业项目:

一般:

拆。

例如:


User

↓

Company

↓

Department

↓

Employee

每一级:

只传:

需要:

对象。

例如:

Department:

只拿:


department

而不是:


user

这样:

依赖:

更小。

性能:

更高。


8.8 一个经典错误

例如:


@Observed
class User{

    address=new Address()

}

按钮:


this.user.address = new Address()

很多人:

认为:

刷新。

实际上:

这里:

变化的是:


address引用

不是:


city

如果新的 Address 本身也是可观察对象,并且组件重新建立了依赖关系,UI 可以刷新;但如果后续修改的是新对象内部未建立观察关系的成员,仍可能不会触发预期更新。

因此:

企业开发:

一般:

修改:


this.user.address.city="上海"

而不是:

不断:


new Address()

除非:

真的:

切换:

整个对象。


8.9 深层对象更新性能分析

假设:


Order

↓

Goods

↓

Spec

↓

SKU

↓

Price

修改:


price.value=9999

真正:

刷新:


PriceComponent

不会:


OrderComponent

GoodsComponent

SpecComponent

全部:

刷新。

这是:

声明式UI:

最大的优势:

按依赖更新,而不是整树更新。


8.10 企业最佳 Model 设计

推荐:


User
 ├── BaseInfo
 ├── Address
 ├── Company
 ├── Wallet
 ├── Permission

不要:


一个User

200多个字段

应该:

拆。

每一个:

Model:

都是:


@Observed

这样:

每个:

模块:

ObjectLink:

自己的:

Model。

例如:


UserInfo

↓

BaseInfo

地址:


AddressPage

↓

Address

钱包:


WalletPage

↓

Wallet

不会:

全部:

依赖:

一个:

巨大:

User。


8.11 多层对象设计原则(企业项目推荐)

在大型 HarmonyOS 项目中,建议遵循以下原则:

原则 推荐做法
模型拆分 一个职责一个 Model,不要把所有字段都放进 User
可观察对象 需要独立更新的 Model 使用 @Observed
数据传递 父组件持有 @State,子组件使用 @ObjectLink
引用粒度 传递最小需要的对象,而不是整个根对象
更新方式 优先修改对象属性,而不是频繁替换整个对象
组件职责 一个组件只关注一个业务对象

这样不仅更新效率更高,也更符合 MVVM 和组件化开发思想。


本章总结

本章重点解决了企业开发中最常见的问题:

✅ 为什么一层对象容易刷新,而多层对象容易失效
✅ 多层嵌套对象如何设计可观察模型
✅ 如何逐层传递 @ObjectLink
✅ 为什么不要把整个根对象一直往下传
✅ 如何设计大型商城、OA、IM 项目的数据模型

第九章 对象数组、List、ForEach 多层嵌套刷新原理(企业级实战)

前面几章,我们已经解决了:

  • 对象嵌套对象
  • @Observed
  • @ObjectLink
  • 多层对象引用

但是,真正开发企业项目时,90%以上的数据都不是单个对象,而是:


List<User>

List<Order>

List<Message>

List<Goods>

List<Comment>

真正让无数 HarmonyOS 开发者头疼的,并不是对象。

而是:

对象数组(Array)

很多开发者都遇到过这样的场景:


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

结果:


页面没有刷新!!

为什么?

这一章,我们彻底讲透 HarmonyOS 数组渲染机制。


9.1 List 为什么比对象复杂?

一个对象:


User

只有:


User

↓

name

↓

age

↓

phone

但是:

数组:


User[]

实际上:


Array

↓

User1

↓

User2

↓

User3

↓

User4

每一个:

又是:


User

↓

Address

↓

Company

所以:

真正的数据结构:


Array

↓

User

↓

Address

↓

Company

复杂度:

立即提升。


9.2 一个经典案例

例如:


@Observed
class User{

    name:string=""

}

页面:


@State
users:User[]=[
    new User(),
    new User(),
    new User()
]

List:


List(){

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

        ListItem(){

            Text(item.name)

        }

    })

}

按钮:


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

很多人:

发现:


没有刷新

为什么?


9.3 ForEach 到底干了什么?

很多人认为:

ForEach:

就是:


for()

其实:

不是。

真正:

流程:


Array

↓

生成组件

↓

建立Key

↓

记录引用

↓

RenderNode

因此:

ForEach:

真正依赖:

不是:


item.name

而是:


Array

所以:

如果:

数组:

没有:

变化。

ForEach:

认为:


没有新增

没有删除

没有移动

于是:

不会:

重新生成。


9.4 为什么修改 name 不刷新?

例如:

数组:


users

↓

User1

↓

name

修改:


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

实际上:

Array:

没有变化。

变化的是:


User1.name

如果 User1 已经通过 @Observed 建立了观察关系,并且列表项组件正确依赖这个对象,界面可以更新;如果只是简单地在 ForEach 中渲染,而没有建立对应的对象观察关系,就容易出现“不刷新”的现象。

因此:

很多人误以为:


Array刷新

实际上:

真正:

变化的是:

对象。


9.5 企业项目正确写法

例如:


@Observed
class User{

    name=""

}

子组件:


@Component
struct UserItem{

    @ObjectLink
    user:User

    build(){

        Text(this.user.name)

    }

}

List:


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

    UserItem({

        user:item

    })

})

这里:

真正:

刷新的是:


UserItem

不是:

整个:

List。


9.6 为什么官方推荐 Item 拆组件

很多新人:

直接:


ForEach(){

    Text(item.name)

    Text(item.phone)

    Text(item.age)

    Text(item.city)

}

这样:

实际上:

整个:

Item:

都是:

一个:

Build。

企业:

一般:

拆:


UserItem

里面:


@ObjectLink
user

这样:

修改:


user.phone

刷新:

只有:


UserItem

不会:

整个:

List。

性能:

提升:

很多。


9.7 数组新增为什么能刷新?

例如:


this.users.push(
    new User()
)

为什么:

刷新?

因为:

Array:

发生:


长度变化

即:


3

↓

4

ForEach:

立即:

发现:


新增节点

于是:

创建:

新的:

RenderNode。

因此:

刷新。


9.8 删除为什么刷新?

例如:


this.users.splice(1,1)

Array:

发生:


删除

ForEach:

重新:

Diff。

于是:


删除RenderNode

刷新。


9.9 排序为什么刷新?

例如:


this.users.sort()

Array:

顺序:

变化。

ForEach:

重新:

比较:


Key

重新:

布局。

因此:

刷新。


9.10 为什么 Key 非常重要?

例如:


ForEach(

    this.users,

    (item)=>{

    }

)

很多人:

没有:

Key。

实际上:

官方推荐:


ForEach(

    this.users,

    (item)=>{

    },

    item=>item.id

)

为什么?

例如:

数组:


1

2

3

新增:


0

1

2

3

没有:

Key。

Diff:

认为:


全部变化

于是:

整个:

List:

刷新。

有:

Key:


id

只:

新增:

一个:

节点。

性能:

高很多。

建议:每个列表项都应有稳定且唯一的 Key,不要使用会变化的值作为 Key,更不要在数据会插入、删除、排序时使用数组下标作为 Key。


9.11 二维数组怎么办?

例如:


Order[]

↓

Goods[]

↓

SKU[]

真正:

企业:

数据:


订单

↓

商品

↓

规格

不要:

直接:


ForEach(){

    ForEach(){

        ForEach(){

        }

    }

}

推荐:

拆:


OrderItem


GoodsItem


SkuItem

每一级:

都是:


@ObjectLink

整个:

依赖:

最小。


9.12 聊天列表最佳实践

例如:

微信:


Message[]

↓

MessageItem

消息:


@Observed
class Message{

    content=""

    status=0

    progress=0

}

MessageItem:


@ObjectLink
message

上传:


message.progress=50

刷新:

只有:

这一条:

消息。

不会:

整个:

聊天:

页面。

这是:

企业:

IM:

标准:

做法。


9.13 商品列表最佳实践

例如:

商城:


Goods[]

↓

GoodsItem

商品:


Goods{

    price

    stock

    sales

    favorite
}

点击:

收藏:


goods.favorite=true

真正:

刷新:


GoodsItem

不是:

500个:

商品。


9.14 List 性能优化建议

大型列表(几百到几千条数据)建议遵循以下原则:

场景 推荐做法
商品列表 每个 Item 独立组件
聊天列表 MessageItem + @ObjectLink
通讯录 ContactItem + 唯一 Key
订单列表 OrderItem 拆分业务模块
嵌套列表 多级组件拆分,不要在一个组件中写多层 ForEach

另外,对于超长列表,应结合 LazyForEach 等按需加载能力,避免一次性创建所有节点。


9.15 本章总结

这一章我们解决了 HarmonyOS 列表开发中最常见的问题:

✅ 为什么对象数组刷新比单对象复杂
ForEach 的工作机制
✅ 为什么修改数组元素属性有时不会刷新
✅ 为什么要把 Item 拆成独立组件并配合 @ObjectLink
pushsplicesort 为什么能触发列表更新
✅ 为什么稳定的 Key 对性能至关重要
✅ 聊天、商城、通讯录等企业级列表最佳实践

第十章 @Observed + @ObjectLink 底层实现原理(ArkUI 渲染机制深度解析)

前面几章,我们已经能够熟练使用:

  • @State
  • @Observed
  • @ObjectLink
  • ForEach
  • 多层对象
  • 多层数组

但是很多人仍然有一个疑问:


为什么修改一个属性,

页面就知道刷新了?

它到底是怎么知道哪个组件需要更新?

为什么不是整个页面重新Build?

如果把这些问题弄懂,你对 HarmonyOS ArkUI 的理解就会上升一个层次。

说明:本章根据 ArkUI 官方公开的状态管理机制、声明式 UI 原理以及框架行为进行分析,部分内部实现为原理推导,并非官方公开源码。


10.1 一个属性修改,到底发生了什么?

例如:


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

页面:


@State
user = new User()

UI:


Text(this.user.name)

点击:


this.user.name = "Jack"

很多人认为:


name变化

↓

刷新页面

实际上,中间还有很多步骤。

完整流程:


修改属性

↓

Observed 捕获变化

↓

通知依赖管理器

↓

标记组件 Dirty

↓

等待下一帧

↓

执行 Build()

↓

Diff

↓

更新 RenderNode

↓

GPU 绘制

↓

屏幕刷新

这就是 ArkUI 一次完整的更新流程。


10.2 Build() 并不是绘制页面

很多新人认为:


Build()

=

绘制UI

其实不是。

Build 更准确来说是:

重新计算 UI 描述树。

例如:


Column() {

    Text(user.name)

    Text(user.age)

}

Build 后得到的不是:


真正按钮

真正文字

而是:

一种:


UI描述信息

例如:


Column

├──Text("Tom")

└──Text("18")

然后:

交给:

Diff。


10.3 什么是 Diff?

假设:

第一次:


Column

├──Text("Tom")

└──18

第二次:


Column

├──Text("Jack")

└──18

Diff:

立即:

发现:


Tom

↓

Jack

只有:

第一行:

不同。

于是:

只更新:


Text

不会:

重新创建:


Column

因此:

速度:

非常快。


10.4 Dirty 是什么意思?

ArkUI:

不会:

每一次:

修改:

立即:

Build。

例如:


user.name="A"

user.age=18

user.phone="123"

不会:


Build()

Build()

Build()

否则:

性能:

极差。

真正:

流程:


name变化

↓

Dirty

继续:


age变化

↓

Dirty

继续:


phone变化

↓

Dirty

最后:

下一帧:

统一:


Build()

因此:

一次:

刷新。

这就是:

Dirty 标记。


10.5 为什么 Build 不是整个页面?

假设:

页面:


Home

├──Header

├──User

├──Goods

├──Footer

修改:


user.name="Tom"

很多人:

认为:


Home重新Build

实际上:

不是。

真正:

Dirty:

的是:


User

所以:

重新:

Build:


User

Header:

不会:

动。

Footer:

不会:

动。

Goods:

不会:

动。


10.6 依赖收集(Dependency Collection)

这是整个 ArkUI 状态管理最重要的概念之一。

例如:


Text(this.user.name)

Build 阶段:

框架会记录:


当前组件

↓

依赖

↓

user.name

如果另一个组件:


Text(this.user.age)

记录:


user.age

因此:

修改:


user.name="Jack"

理论上:


name

↓

通知

↓

第一个组件

第二个:

不会:

收到。

这就是:

依赖收集。


10.7 ObjectLink 为什么效率高?

例如:

父组件:


@State
user=new User()

子组件:


@ObjectLink
user

实际上:

不是:


复制对象

而是:


共享引用

内存:


0x10086

↓

User

父:


↓

0x10086

子:


↓

0x10086

修改:


user.name="Tom"

无需:


复制

序列化

重新创建

直接:

通知:

Observer。

因此:

性能:

非常高。


10.8 为什么不能一直 new 对象?

例如:


this.user = new User()

意味着:


旧对象

↓

ObserverA

变成:


新对象

↓

ObserverB

所有:

依赖:

重新:

建立。

因此:

成本:

非常高。

所以:

企业:

推荐:

修改:


user.name="Tom"

而不是:


user=new User()

除非:

整个:

业务:

真的:

换人。

例如:

登录:

切换账号。


10.9 ObjectLink 如何找到需要更新的组件?

假设:

页面:


Home

↓

UserCard

↓

Avatar

↓

Name

所有:

都:


引用:

User

真正:

内部:

可以理解成:


User

↓

Observer

↓

依赖列表

↓

UserCard

↓

Avatar

↓

Name

修改:


user.avatar="xxx"

Observer:

遍历:

依赖。

通知:

真正:

使用:

avatar:

组件。

因此:

不是:

整个:

页面:

刷新。


10.10 为什么声明式 UI 比传统 UI 更快?

传统 UI:

例如 Android View:


findViewById()

↓

setText()

↓

invalidate()

↓

measure()

↓

layout()

↓

draw()

开发者:

需要:

自己:

控制。

而:

ArkUI:

只需要:


user.name="Tom"

剩下:

全部:

框架:

完成。

开发者:

不用:

关心:


刷新哪个Text

哪个Layout

只关心:


数据

这就是:

声明式UI。


10.11 与 Vue3、React、Flutter 对比

框架 状态变化 更新机制
Vue3 reactive() Proxy + 依赖收集
React setState() Fiber + Diff
Flutter setState() Widget 重建
HarmonyOS ArkUI @State / @Observed 状态驱动 + Build + Diff

可以发现:

ArkUI:

思想:

其实:

非常接近:

现代:

声明式UI。

只是:

状态:

通过:


@State

@Observed

@ObjectLink

组织。


10.12 企业项目如何减少无效刷新?

很多团队在项目初期会把所有数据都放到一个巨大的 UserAppState 中,结果一个小字段变化,很多组件都依赖它,维护成本越来越高。

推荐的做法是:


AppState
│
├── UserState
├── OrderState
├── GoodsState
├── MessageState
└── SettingState

组件只依赖自己需要的数据,例如:


UserPage
    ↓
UserState

OrderPage
    ↓
OrderState

MessagePage
    ↓
MessageState

这样可以:

  • 降低组件之间的耦合
  • 减少不必要的依赖关系
  • 更容易定位刷新问题
  • 更适合团队协作开发

10.13 常见误区总结

错误写法 原因 推荐做法
深层对象未声明为可观察 修改属性无法建立观察关系 需要响应更新的模型使用 @Observed
子组件重新 new 一个对象 切断引用链 修改对象属性,不要随意替换实例
ForEach 不设置稳定 Key Diff 效率低,可能复用错误 使用业务唯一 ID
一个组件负责整个页面所有数据 Build 范围过大 拆分组件,缩小依赖范围
一个超大的 User Model 状态耦合严重 按业务拆分多个 Model

第十一章 HarmonyOS 状态管理十大经典踩坑案例(100+问题解析)

经过前面十章,我们已经从:

  • @State
  • @Observed
  • @ObjectLink
  • 多层对象
  • 数组
  • List
  • ForEach
  • Diff机制

完整理解了 ArkUI 的响应式渲染体系。

但是在真实项目中,理论懂了以后,仍然会遇到大量:


为什么不刷新?
为什么刷新了又回去了?
为什么偶现?
为什么开发环境正常,真机异常?

这些问题。

本章结合企业项目开发经验,总结 HarmonyOS 状态管理中最高频的问题。


一、@Observed 不生效的常见原因

1.1 忘记给嵌套对象添加 @Observed

这是最高频的问题。

错误:


@Observed
class User {

    address: Address = new Address()

}


class Address {

    city:string="北京"

}

数据:


User

↓

Address

↓

city

修改:


user.address.city="上海"

可能:

无法触发更新。

原因:

只有:


User

可观察。

但是:


Address

不是。


正确:


@Observed
class Address {

    city:string="北京"

}


@Observed
class User {

    address:Address = new Address()

}

现在:


User

↓

Address

↓

city

整个链路:

都是可观察对象。


1.2 只修改对象内部属性,没有触发引用变化

例如:


class User{

    name:string="Tom"

}

页面:


@State
user:User=new User()

修改:


this.user.name="Jack"

发现:

不更新。

原因:

普通 class:

只是:

普通 JavaScript 对象。

ArkUI:

不知道:

发生了变化。


解决:

方式一:


@Observed
class User{

}

方式二:

重新赋值:


this.user={
    ...this.user,
    name:"Jack"
}

但是:

对于复杂 Model:

推荐:

@Observed


二、@ObjectLink 常见失效问题


2.1 子组件没有使用 @ObjectLink

错误:

父组件:


@State
user=new User()

传递:


UserCard({
    user:this.user
})

子组件:


@Component
struct UserCard{

    user:User

}

这里:

只是普通属性。

组件:

不会建立响应关系。


正确:


@Component
struct UserCard{

    @ObjectLink
    user:User

}

这样:

建立:


User

↓

Observer

↓

UserCard

2.2 给 ObjectLink 重新赋值

错误:


@ObjectLink
user:User


this.user=new User()

发生:

引用替换。

原关系:


Parent

↓

UserA

↓

Child

变:


Parent

↓

UserA


Child

↓

UserB

两个对象:

分离。


正确:

修改属性:


this.user.name="Jack"

2.3 ObjectLink 传递错误对象

例如:

父:


@State
user=new User()

传:


UserCard({
    user:this.user.copy()
})

错误。

因为:

copy:

产生:

新对象。

子组件:

拿到:

不是:

父组件:

管理的对象。


正确:


UserCard({
    user:this.user
})

三、ForEach 列表刷新问题


3.1 修改数组元素属性不刷新

错误:


@State
users=[
 {
   name:"Tom"
 }
]


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

原因:

数组:

没有变化。

对象:

也不是:

Observed。


方案:

定义:


@Observed
class User{

    name=""

}

数组:


@State
users:User[]=[]

Item:


@ObjectLink
user:User

3.2 push对象以后无法更新

例如:


this.users.push({

name:"Tom"

})

普通对象:

没有:

观察能力。

后续:

修改:


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

可能:

不更新。


推荐:


this.users.push(
    new User()
)

3.3 使用数组下标作为Key

错误:


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

例如:

原:


A
B
C

删除:

A:

变:


B
C

框架:

认为:

第0个:

还是:

A的位置。

可能:

导致:

状态错乱。


正确:

使用:

业务ID:


item=>item.id

例如:


10001

10002

10003

四、多层对象更新失败


4.1 五层对象只Observed第一层

错误:


@Observed
class User{

    company=new Company()

}


class Company{

    department=new Department()

}

结构:


User

↓

Company

↓

Department

修改:


user.company.department.name="研发部"

可能:

失败。


正确:

每层:


@Observed
class Company{

}


@Observed
class Department{

}

4.2 一个巨大Model导致刷新范围过大

错误:


class AppState{

 user

 order

 goods

 message

 setting

}

所有页面:

引用:

AppState。

结果:

修改:

message:

可能:

影响:

大量组件。


推荐:

拆:


UserState

OrderState

GoodsState

MessageState

五、异步更新问题

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


5.1 网络请求回来以后页面没刷新

例如:


async load(){

 let data=await request()

 this.user=data

}

如果:

data:

普通对象。

后续:


data.name="Tom"

可能:

没有响应。


推荐:

转换:


this.user=new User(data)

其中:

User:

必须:

@Observed。


5.2 定时器修改对象

例如:


setInterval(()=>{

this.message.count++

},1000)

如果:

message:

不是:

Observed。

不会:

刷新。


正确:


@Observed
class Message{

count=0

}

六、@Builder 中状态更新问题

很多项目:

喜欢:

抽取:

@Builder。

例如:


@Builder
function UserView(user:User){

 Text(user.name)

}

然后:

发现:

不刷新。

原因:

@Builder:

不是:

完整组件。

它:

没有:

独立生命周期。


复杂业务:

推荐:

拆成:

@Component。

例如:


@Component
struct UserView{

@ObjectLink
user:User

}

七、页面返回后状态丢失

例如:

页面:

A

进入:

B

返回:

A

发现:

数据恢复默认。

原因:

状态:

属于:

组件生命周期。


解决:

使用:

持久化状态:

例如:

  • AppStorage
  • PersistentStorage
  • 数据库
  • 状态管理层

八、@State 不应该存放大型对象

错误:


@State
appData=new HugeModel()

里面:

几百字段。

结果:

任何变化:

影响:

整个页面。


推荐:

页面:

只保存:

当前需要:


@State
visible:boolean

复杂数据:

放:

ViewModel。


九、状态更新最佳实践

企业项目推荐:

结构:


Page
 |
 ViewModel
 |
 Model(@Observed)
 |
 Component(@ObjectLink)

例如:


GoodsPage

↓

GoodsViewModel

↓

Goods

↓

GoodsItem

职责:

清晰。


十、状态管理排查口诀

遇到:

“不刷新”

按照顺序检查:


第一步

对象是不是:


@Observed

第二步

子组件是不是:


@ObjectLink

第三步

有没有:

重新:


new

对象。


第四步

数组:

有没有:

稳定Key。


第五步

嵌套对象:

每层:

有没有:

观察能力。


第六步

是不是:

@Builder导致。


本章总结

HarmonyOS 状态管理问题,本质可以归纳为三个核心:

1. 有没有观察

对应:


@Observed

2. 有没有连接

对应:


@ObjectLink

3. 有没有正确依赖

对应:


组件拆分

Key设计

Model设计

掌握这三个原则,90%以上的 ArkUI 刷新问题都可以快速定位。

第十二章 @State、@Prop、@Link、@ObjectLink、@Provide/@Consume 全状态管理体系深度解析

在 HarmonyOS ArkUI 开发中,状态管理一直是面试和实际项目中的重点。

很多开发者会背:


@State 本地状态

@Prop 单向传递

@Link 双向绑定

@ObjectLink 对象引用

@Provide/@Consume 跨层传递

但是实际开发中,经常出现:


什么时候用@State?

为什么Prop改不了?

为什么ObjectLink必须配Observed?

父子三层组件怎么办?

Provide是不是全局变量?

本章将完整分析 ArkUI 状态管理体系。


12.1 ArkUI 状态管理整体模型

HarmonyOS ArkUI 的状态管理,可以理解成:


                    状态来源

                       |
                       |
                  @State
                       |
        --------------------------------
        |              |              |
      @Prop          @Link       @ObjectLink
        |              |              |
     单向数据       双向数据        对象引用
        |
        |
   @Provide/@Consume
        |
     跨层级共享

不同装饰器解决不同问题。


12.2 @State:组件自己的状态

定义

@State

表示:

当前组件内部拥有的数据。

例如:


@Component
struct Counter {

    @State
    count:number = 0


    build(){

        Column(){

            Text(`${this.count}`)

            Button("增加")
            .onClick(()=>{

                this.count++

            })

        }

    }

}

这里:

Counter:

自己拥有:


count

数据关系

结构:


id="x0n2yr"
Counter

拥有

↓

count

没有:

父组件。

没有:

外部依赖。


适合场景

例如:

1. 页面显示状态


@State
showDialog=false

2. 输入框内容


@State
text=""

3. 当前选择项


@State
currentIndex=0

12.3 @Prop:父传子单向数据

例如:

父:


@Component
struct Parent {


@State
name="Tom"


build(){

    Child({
        name:this.name
    })

}

}

子:


@Component
struct Child {


@Prop
name:string


build(){

Text(this.name)

}

}

数据:

方向:


id="x7h3ui"
Parent

↓

Child

@Prop特点

子组件:

可以读取:


this.name

但是:

不应该:

修改:


this.name="Jack"

因为:

数据所有权:

属于:

父组件。


12.4 @Link:父子双向状态绑定

例如:

父:


@State
checked=false

子:


@Link
checked:boolean

关系:


id="y3k4om"
Parent

⇄

Child

双方:

都可以:

修改。


案例:

Switch组件:


@Component
struct MySwitch {


@Link
value:boolean


build(){

Toggle({
    isOn:this.value
})

}


}

父:


MySwitch({
    value:$checked
})

这里:

$

表示:

双向绑定。


12.5 @ObjectLink:对象共享

前面详细讲过。

核心:


id="v1f2cr"
不是传值

而是传对象引用

例如:


@Observed
class User{

name="Tom"

}

父:


@State
user=new User()

子:


@ObjectLink
user:User

关系:


id="9v3v7g"
User对象

      ↑

      |

Parent

      |

      ↓

Child

12.6 @ObjectLink 和 @Link区别

这是面试高频问题。

项目 @Link @ObjectLink
数据类型 普通状态 对象实例
是否需要Observed
传递方式 双向绑定 引用共享
使用场景 Boolean/String/Number Model对象
复杂对象 不推荐 推荐

例如:

简单:


@Link
visible:boolean

复杂:


@ObjectLink
user:User

12.7 @Provide/@Consume:跨层共享

假设:

组件:

五层:


id="p6ut9m"
Page

↓

A

↓

B

↓

C

↓

D

但是:

D:

需要:

Page的数据。

传统:

需要:

一层层传:


Page

↓

A

↓

B

↓

C

↓

D

非常麻烦。


使用:

Provide:

提供:


@Provide
user:User=new User()

Consume:

接收:


@Consume
user:User

关系:

变成:


id="h95z84"
Page

        |

        |

        ↓

        D

中间:

组件:

不用关心。


12.8 Provide是不是全局变量?

不是。

这是很多人的误解。

错误理解:


Provide = 全局状态

实际上:

它属于:

组件树范围。

例如:


App

↓

PageA

↓

Child

Provide:

只影响:

这棵树。

离开:

页面:

生命周期结束。


真正全局:

应该使用:

例如:

  • AppStorage
  • PersistentStorage
  • 状态管理框架

12.9 五大装饰器选择原则

实际项目:

可以使用:

下面规则。


场景1:组件自己使用

使用:


@State

例如:


loading=false

场景2:父传子只读

使用:


@Prop

例如:


title

场景3:父子双方修改

使用:


@Link

例如:


checked

场景4:复杂对象共享

使用:


@ObjectLink

例如:


User

Order

Goods

场景5:跨很多层

使用:


@Provide
@
Consume

例如:


主题

语言

用户信息

12.10 企业项目推荐组合

一个商城项目:


GoodsPage

@State
    ↓

GoodsViewModel

@Observed
    ↓

Goods

@ObjectLink
    ↓

GoodsItem

@ObjectLink

例如:


页面状态

↓

业务Model

↓

组件共享

12.11 一个完整案例

需求:

商品详情页:

  • 页面控制加载状态
  • 商品信息多个组件共享
  • 收藏按钮修改商品状态
  • 底部购买按钮读取商品价格

结构:


GoodsPage

|
|
@State loading


|
|
Goods


|
|
GoodsInfo

@ObjectLink goods


FavoriteButton

@ObjectLink goods


BuyButton

@ObjectLink goods

数据:


@Observed
class Goods{

title:string

price:number

favorite:boolean

}

修改:

收藏:


goods.favorite=true

结果:

所有:

依赖:

favorite:

组件刷新。


12.12 面试高频问题

Q1:@State和@ObjectLink区别?

答案:


@State负责拥有状态

@ObjectLink负责引用@Observed对象

Q2:为什么ObjectLink需要Observed?

因为:


ObjectLink只负责连接

Observed负责监听变化

Q3:Prop为什么不能修改?

因为:


Prop表示父组件单向输入

数据所有权属于父组件

Q4:Provide是不是单例?

不是:


它属于组件树上下文

Q5:大型对象应该放State吗?

不推荐:

应该:


ViewModel + Observed Model

本章总结

HarmonyOS 状态管理核心:

装饰器 核心作用
@State 自己拥有状态
@Prop 父传子
@Link 父子双向
@ObjectLink 共享对象引用
@Provide/@Consume 跨层共享

记忆口诀:

自己的用 State,别人传的用 Prop,需要双改用 Link,复杂对象用 ObjectLink,跨很多层用 Provide。

第十三章 HarmonyOS ArkUI 状态管理企业级架构设计(从组件开发到大型 App 架构)

前面十二章,我们解决的是:

一个组件、一个页面、一个对象如何正确刷新。

但是企业项目真正面对的问题是:


id="4y7s9h"
几十个页面

几百个组件

几千个Model

多人协作开发

网络数据实时变化

本地缓存同步

登录状态共享

这时候,如果只是简单:


@State
user = new User()

整个项目很快会陷入混乱。

典型问题:


id="o9q6mm"
A页面修改用户信息

↓

B页面不知道

↓

C页面数据旧了

↓

D页面重新请求

↓

状态越来越乱

所以企业级 HarmonyOS 应用必须建立:

清晰的数据流和状态架构。


13.1 企业级 ArkUI 推荐架构

目前 HarmonyOS 企业项目常见架构:


id="w1t5xg"
                    UI层

              ArkUI Component

                    |
                    |
              ViewModel层

          状态管理 + 页面逻辑

                    |
                    |
              Repository层

          数据组合与业务入口

                    |
                    |
              DataSource层

       网络 / 数据库 / 系统能力

完整:


id="4u6x0o"

Page

↓

ViewModel

↓

Repository

↓

Network

↓

Database

13.2 为什么不能把逻辑全部写页面?

很多初学者:

页面:


@Component
struct UserPage{

    @State user=new User()

    async load(){

        let result=await http()

        this.user=result

    }

}

刚开始:

没有问题。

但是半年后:

页面:

可能变成:


id="p1qj5d"
UserPage

5000行代码

↓

登录

↓

请求

↓

缓存

↓

数据库

↓

刷新

↓

权限

↓

弹窗

↓

跳转

维护困难。


企业项目:

应该:

拆:


id="4ry4bq"
UserPage

负责:

UI展示


UserViewModel

负责:

状态和逻辑


UserRepository

负责:

数据来源

13.3 ViewModel 是状态管理核心

例如:

用户页面:


id="z5t2ne"

UserPage

↓

UserViewModel

↓

User

定义 Model:


id="3g5x2z"
@Observed
export class User {

    name:string=""

    avatar:string=""

}

ViewModel:


id="h8q7px"
export class UserViewModel {


    user:User=new User()


    loading:boolean=false



    async loadUser(){

        this.loading=true


        let data =
        await UserRepository.getUser()


        this.user.name=data.name

        this.user.avatar=data.avatar


        this.loading=false

    }


}

页面:


id="v0r5xh"
@Component
struct UserPage{


    vm:UserViewModel=
    new UserViewModel()


    build(){

        Column(){


            Text(this.vm.user.name)


        }

    }

}

这样:

页面:

只负责:

显示。


13.4 ViewModel 为什么适合 @Observed?

因为:

ViewModel 本身:

也是:

状态对象。

例如:


id="7t1m5k"
@Observed
class UserViewModel{


}

然后:

页面:


@State
vm=new UserViewModel()

组件:

可以:

观察:

ViewModel。


结构:


id="v75w0h"
ViewModel

↓

State

↓

Component

13.5 Repository 层作用

很多项目:

直接:

页面请求:


http.get()

问题:

以后:

接口变化:

全部页面修改。


Repository:

负责:

统一数据入口。

例如:


id="i8q6x3"
UserRepository

       |
       |

 ----------------

 |              |

网络接口      本地缓存

页面:

不用知道:

数据来自哪里。


例如:


id="4r3n7h"
class UserRepository{


static async getUser(){

    let cache=
    await Cache.getUser()


    if(cache){

        return cache

    }


    return await Api.getUser()

}

}

13.6 网络数据如何转换成 Observed Model?

这是企业项目非常重要的一点。

服务器返回:

JSON:


id="3qzj7x"
{
 "name":"Tom",
 "avatar":"xxx"
}

不要直接:


this.user=data

原因:

data:

只是:

普通Object。


推荐:

转换:


id="l7f2d4"
let user =
new User()

user.name=data.name

user.avatar=data.avatar

然后:

交给:


id="2k5l8n"
@Observed
User

13.7 大型项目状态分类

企业项目:

状态不要全部混在一起。

推荐:

分:


1. 页面状态

例如:


id="7h1n8k"
loading

dialogVisible

currentTab

属于:

页面。

使用:


@State

2. 业务状态

例如:


id="j4z0d1"
用户

订单

购物车

使用:


@Observed

3. 全局状态

例如:


id="4j7z0q"
登录用户

主题

语言

权限

使用:


AppStorage

Provide

状态管理层

13.8 一个商城 App 状态设计案例

大型商城:


id="0z7w1v"

AppState

|

├── UserState

│       |

│       User


├── CartState

│       |

│       Cart


├── OrderState

│       |

│       Order


├── GoodsState

│       |

│       Goods


└── ConfigState

页面:

商品:


id="x3v5np"

GoodsPage

↓

GoodsState

↓

Goods

↓

GoodsItem

购物车:


id="5n8z0f"

CartPage

↓

CartState

↓

Cart

互不污染。


13.9 如何避免状态无限传递?

错误:


id="7b5y3n"

App

↓

Page

↓

A

↓

B

↓

C

↓

D

↓

E

每层:

传:


user

最终:

组件:

全部:

知道:

User。


正确:

使用:

模块状态。

例如:


id="w9z3f1"

UserModule

↓

UserState

↓

UserComponent

需要:

直接:

获取。


13.10 Component 设计原则

企业组件:

应该:

小。

例如:

错误:


id="k8y4m6"
GoodsPage

2000行

正确:


id="n5x1p8"

GoodsPage

|

├──GoodsBanner

├──GoodsInfo

├──GoodsPrice

├──GoodsSpec

├──GoodsComment

└──GoodsButton

每个组件:

负责:

自己的数据。


13.11 ObjectLink 在企业架构中的位置

完整:


id="2h4v7q"

ViewModel

↓

Observed Model

↓

ObjectLink

↓

Component

例如:


id="q0w8m3"

GoodsViewModel

↓

Goods(@Observed)

↓

GoodsInfo(@ObjectLink)

↓

Price(@ObjectLink)

这是:

最推荐:

组合方式。


13.12 企业级状态更新流程

一次商品价格变化:

例如:

后台:

修改价格。

流程:


id="n7v5s2"

WebSocket

↓

Repository

↓

GoodsViewModel

↓

Goods(@Observed)

↓

ObjectLink

↓

GoodsPrice

↓

UI刷新

页面:

完全:

不用:

手动刷新。


13.13 面试问题

Q1:

为什么 ViewModel 不直接操作 UI?

答案:

因为:

ViewModel:

应该:

独立于 UI。

方便:

测试。


Q2:

为什么 Model 使用 @Observed?

因为:

Model:

需要:

被组件观察。


Q3:

为什么不要所有数据放 AppStorage?

因为:

全局状态:

会导致:

依赖过大。


Q4:

ObjectLink 应该传整个对象吗?

推荐:

传递:

最小业务对象。

不要:

传:

整个 AppState。


13.14 本章总结

企业级 HarmonyOS 应用推荐:


id="v4m6n8"

UI

↓

ViewModel

↓

Repository

↓

DataSource

状态:

分类:


页面状态

↓

@State


业务状态

↓

@Observed


跨模块状态

↓

AppStorage / Provide

组件:

使用:


@ObjectLink

共享:

业务对象。

第十四章 HarmonyOS @Observed + @ObjectLink 与 Vue3、React、Flutter 状态管理深度对比(架构思想篇)

前面十三章,我们已经完整掌握了 HarmonyOS ArkUI 的状态管理。

但是,如果想真正成为高级开发者,仅仅会使用 ArkUI 还远远不够。

你还需要理解:

为什么 ArkUI 要这样设计?为什么不用 Vue 那种方式?为什么不像 Flutter 那样全部重建 Widget?为什么 React 又采用 Fiber?

理解这些框架背后的设计思想,才能真正掌握声明式 UI 的本质。


14.1 四大声明式 UI 框架

目前主流声明式 UI 框架:


Vue3

React

Flutter

HarmonyOS ArkUI

它们都遵循同一个原则:

UI = f(State)

也就是说:


界面 = 数据的函数

数据变化:

UI 自动更新。

开发者:

不用:


findView()

setText()

notifyDataSetChanged()

invalidate()

这些传统命令式操作。


14.2 为什么传统 UI 越写越复杂?

例如 Android View:

修改昵称:


user.setName("Tom");

TextView tv=findViewById(...);

tv.setText(user.getName());

如果还有:

头像:

年龄:

积分:

VIP:

全部:

需要:

自己:

刷新。

流程:


修改数据

↓

找到控件

↓

刷新控件

↓

刷新布局

问题:

UI:

和:

数据:

严重耦合。


声明式:

变成:


user.name="Tom"

结束。

剩下:

框架:

完成。


14.3 Vue3 的状态管理

Vue3:

核心:


reactive()

ref()

例如:


const user=reactive({

name:"Tom"

})

页面:


{{user.name}}

修改:


user.name="Jack"

Vue:

立即:

知道:


name变化

为什么?

因为:

Vue3:

底层:

使用:


Proxy

流程:


Proxy

↓

set()

↓

trigger()

↓

effect()

↓

组件刷新

14.4 HarmonyOS 与 Vue3 的区别

Vue:

依赖:


Proxy

Harmony:

依赖:


@Observed

两者:

思想:

很接近。

区别:

Vue3 HarmonyOS
reactive() @Observed
ref() @State
provide/inject @Provide/@Consume
props @Prop
响应式对象 ObjectLink

最大的不同:

Vue:

运行时:

Proxy。

Harmony:

编译期 + 状态装饰器 + 运行时观察机制。

这种方式减少了运行时动态代理带来的开销,同时让状态关系更加明确。


14.5 React 的状态管理

React:

没有:

Proxy。

也没有:

Observed。

React:

只有:


const [count,setCount]=useState(0)

修改:


setCount(count+1)

React:

不知道:

哪个变量:

变化。

React:

只知道:


组件状态更新

于是:

重新执行:


render()

例如:


function User(){

return(

<div>

{name}

{age}

</div>

)

}

render:

重新执行。

再:

Diff。


流程:


setState

↓

Fiber

↓

render()

↓

VirtualDOM

↓

Diff

↓

DOM

14.6 HarmonyOS 与 React 的区别

React:

特点:


组件重新执行

↓

Virtual DOM Diff

Harmony:

特点:


状态变化

↓

Build

↓

依赖分析

↓

Diff

两者:

都:

Diff。

但是:

Harmony:

更加依赖:

状态关系。

React:

更加依赖:

组件重新计算。


14.7 Flutter 的状态管理

Flutter:

最经典:


setState((){

count++;

});

页面:


Text("$count")

Flutter:

重新:

Build:

Widget。

注意:

Flutter:

Widget:

不是:

真正:

View。

它:

也是:

描述树。

流程:


setState

↓

Widget Build

↓

Element

↓

RenderObject

↓

GPU

14.8 Harmony 与 Flutter 的区别

Flutter:

所有:

更新:

入口:

都是:


setState()

Harmony:

入口:

很多:


@State

@Observed

@ObjectLink

Harmony:

状态:

粒度:

更细。

Flutter:

通常:

组件:

重新:

Build。

Harmony:

组件:

根据依赖:

决定:

是否:

Build。


14.9 四大框架底层更新流程对比


Vue3

修改数据

↓

Proxy

↓

effect

↓

Diff

↓

DOM


React

setState

↓

Fiber

↓

render

↓

Diff

↓

DOM


Flutter

setState

↓

Widget

↓

Element

↓

RenderObject

↓

GPU


Harmony

修改状态

↓

Observed

↓

Dirty

↓

Build

↓

Diff

↓

RenderNode

↓

GPU

可以看到:

Harmony 更接近 Flutter 的渲染思路,同时借鉴了 Vue 的响应式理念。


14.10 为什么 HarmonyOS 没有 Virtual DOM?

这是很多人疑惑的问题。

原因:

ArkUI:

不是:

HTML。

没有:


DOM

它:

直接:

生成:


RenderNode

所以:

流程:

更加:

简单:


State

↓

Build

↓

RenderNode

↓

GPU

少了一层:

Virtual DOM。

因此:

性能:

更高。


14.11 为什么 ArkUI 使用装饰器?

例如:


@State

@Observed

@ObjectLink

很多人:

觉得:

写起来:

麻烦。

实际上:

装饰器:

最大的优势:

就是:

声明依赖关系。

编译阶段:

框架:

已经知道:


哪个变量:

属于State

哪个对象:

属于Observed

哪个组件:

属于ObjectLink

因此:

运行时:

不需要:

像 Vue2 那样:

大量:

Object.defineProperty。

也不像 React:

每次:

全部:

render。


14.12 四大框架性能比较

说明:性能受业务场景、实现方式、平台能力等多种因素影响,下表为更新机制特点对比,并非绝对性能排名。

框架 状态监听 更新粒度 渲染特点
Vue3 Proxy 属性级 DOM Diff
React State Hook 组件级 Fiber + Virtual DOM
Flutter setState Widget级 Widget → RenderObject
HarmonyOS ArkUI @Observed + @ObjectLink 状态依赖级 Build + RenderNode

14.13 企业项目如何选择状态设计?

无论使用哪种框架,都建议遵循相同原则:


UI
↓

ViewModel

↓

Model

↓

Repository

而不是:


Page

↓

直接请求网络

↓

直接修改UI

真正优秀的架构:

思想:

其实:

都是:

一致的。


14.14 面试高频问题

Q1:HarmonyOS 为什么不用 Virtual DOM?

答:

ArkUI 面向的是原生渲染树,不需要浏览器 DOM,因此可以直接构建 RenderNode,再通过 Diff 更新渲染节点。


Q2:HarmonyOS 更像 Vue 还是 Flutter?

答:

可以理解为:

  • 状态管理思想更接近 Vue(响应式数据驱动)。
  • 渲染架构更接近 Flutter(声明式 UI + 原生渲染树)。

Q3:为什么要使用 @Observed

答:

因为框架需要明确知道哪些对象需要建立观察关系,从而在属性变化时通知依赖组件,而不是扫描所有对象。


Q4:为什么大型项目推荐 ViewModel?

答:

因为 ViewModel 可以隔离 UI 和业务逻辑,提高可维护性、可测试性,并让多个页面共享一致的数据来源。


本章总结

本章从架构设计角度对比了四大声明式 UI 框架:

✅ Vue3 的 Proxy 响应式机制
✅ React 的 Fiber + Virtual DOM 更新流程
✅ Flutter 的 Widget 重建机制
✅ HarmonyOS ArkUI 的 @Observed + @ObjectLink 更新机制
✅ 四种框架的设计理念与差异
✅ 企业项目如何借鉴这些框架的共同架构思想

到这里,你已经不仅掌握了 ArkUI 的使用方式,也理解了它在现代声明式 UI 框架中的定位。

第十五章 HarmonyOS @Observed@ObjectLink 源码级实现(手写响应式框架)

说明

本章不是 HarmonyOS ArkUI 官方源码解析,因为 ArkUI 的完整内部实现并未公开。

我们将通过一个简化版响应式框架来模拟 ArkUI 的核心思想,包括:

  • Observable(可观察对象)
  • Observer(观察者)
  • Dependency(依赖管理)
  • Watcher(订阅者)
  • Dirty 标记
  • Build 调度
  • Diff 更新

这样可以帮助大家真正理解 ArkUI 状态管理的底层工作方式。


15.1 如果没有响应式框架

假设只有一个普通对象:


class User {
  name: string = "Tom"
}

let user = new User()

user.name = "Jack"

这里发生了什么?

答案:


修改了一个普通对象属性

除此之外,没有任何事情发生。

框架不知道:

  • 谁修改了?
  • 谁依赖它?
  • 哪个组件需要刷新?

因此页面不会自动更新。


15.2 我们需要什么能力?

如果希望:


user.name = "Jack"

自动刷新 UI。

至少需要下面几个能力:


对象变化

↓

通知观察者

↓

找到依赖组件

↓

标记Dirty

↓

下一帧Build

↓

Diff

↓

更新UI

所以第一步:

就是让对象拥有:

通知别人变化的能力。


15.3 第一步:实现 Observer

最简单版本:


class Observer {

    private listeners: (() => void)[] = []

    subscribe(fn: () => void) {
        this.listeners.push(fn)
    }

    notify() {
        this.listeners.forEach(item => item())
    }

}

这里:

保存:

所有:

观察者。

例如:


Observer

↓

ComponentA

↓

ComponentB

↓

ComponentC

变化:

调用:


notify()

全部:

收到。


15.4 第二步:Observable 对象

例如:


class User {

    observer = new Observer()

    private _name = "Tom"

    get name() {
        return this._name
    }

    set name(value: string) {

        this._name = value

        this.observer.notify()

    }

}

这里:

发生:


user.name = "Jack"

实际上:

执行:


set name()

然后:


notify()

于是:

通知:

所有组件。


15.5 第三步:组件订阅

例如:


let user = new User()

user.observer.subscribe(() => {

    console.log("页面刷新")

})

修改:


user.name = "Jack"

输出:


页面刷新

这就是:

最简单:

响应式。


15.6 但是这样效率太低

例如:

页面:


UserPage

↓

头像

↓

昵称

↓

手机号

↓

积分

↓

VIP

修改:


user.vip = true

实际上:

头像:

不会变。

手机号:

不会变。

昵称:

不会变。

但是:

刚才:

全部:

收到:

notify。

浪费。


15.7 Dependency(依赖收集)

所以:

真正框架:

不会:

广播。

而是:

建立:

依赖关系。

例如:


user.name

↓

NameComponent

user.avatar

↓

AvatarComponent

user.vip

↓

VipComponent

修改:


user.vip = true

真正:

通知:


VipComponent

不会:

通知:

其它组件。

这就是:

依赖收集。


15.8 实现 Dependency

例如:


class Dependency {

    private watchers = new Set<Watcher>()

    add(watcher: Watcher) {
        this.watchers.add(watcher)
    }

    notify() {
        this.watchers.forEach(item => item.update())
    }

}

这里:

保存:

真正:

依赖:

当前属性:

组件。

例如:


Dependency(name)

↓

NameComponent

修改:

name。

只:

通知:

NameComponent。


15.9 Watcher(订阅者)

定义:


class Watcher {

    update() {

        console.log("重新Build")

    }

}

真正:

ArkUI:

这里:

不是:

打印。

而是:


Dirty

↓

Build

15.10 Dirty 标记

为什么:

不能:

立即:

Build?

例如:


user.name = "Tom"

user.age = 18

user.phone = "123"

如果:

立即:

刷新。

结果:


Build

Build

Build

性能:

极差。

真正:

框架:

会:


name变化

↓

Dirty

继续:


age变化

↓

Dirty

继续:


phone变化

↓

Dirty

下一帧:

统一:


Build()

15.11 Scheduler(调度器)

我们模拟:


class Scheduler {

    private static queue = new Set<Watcher>()

    static add(watcher: Watcher) {
        this.queue.add(watcher)
    }

    static flush() {

        this.queue.forEach(item => item.update())

        this.queue.clear()

    }

}

以后:

notify:

不是:

立即:

update。

而是:


加入队列

下一帧:

统一:

执行。


15.12 Build 到底生成什么?

很多人:

认为:

Build:

直接:

画页面。

实际上:

生成:


UI Tree

例如:


Column() {

    Text(user.name)

    Button()

}

Build:

得到:


Column

├──Text

└──Button

不是:

真正:

控件。


15.13 Diff 算法

第一次:


Column

↓

Text(Tom)

第二次:


Column

↓

Text(Jack)

Diff:

发现:


Tom

↓

Jack

因此:

更新:

Text。

Column:

不用:

创建。


15.14 ObjectLink 如何共享对象?

例如:

父:


user

地址:


0x10086

子:


@ObjectLink
user

实际上:

也是:


0x10086

内存:


Parent

↓

0x10086

↑

Child

所以:

修改:


user.name = "Jack"

父:

看到。

子:

也看到。

没有:

复制。


15.15 整个更新流程(源码思想)

综合前面的内容,一个状态变化的大致流程可以表示为:


修改属性
        │
        ▼
Setter(可观察对象)
        │
        ▼
Dependency.notify()
        │
        ▼
Watcher.update()
        │
        ▼
Scheduler(加入更新队列)
        │
        ▼
下一帧统一执行 Build
        │
        ▼
生成新的 UI Tree
        │
        ▼
Diff 新旧节点
        │
        ▼
更新 RenderNode
        │
        ▼
GPU 绘制

这就是响应式 UI 框架最核心的工作流程。


15.16 与 ArkUI 的对应关系

注意:下表是概念对应关系,并非官方内部类名。

我们手写的框架 ArkUI 中对应的概念
Observable @Observed 管理的可观察对象
Observer / Dependency 状态变化通知与依赖管理机制
Watcher 使用状态的组件或节点
Scheduler 更新调度机制
Dirty 待更新标记
Build 重新生成 UI 描述
Diff 比较新旧 UI 描述
RenderNode 实际渲染节点

15.17 为什么要理解这些?

很多开发者觉得:


会用就行了。

但真正的大型项目中,你会遇到:

  • 为什么这里只刷新了一个组件?
  • 为什么这个组件没有刷新?
  • 为什么修改对象属性有效,替换对象后又失效?
  • 为什么列表滚动时性能下降?
  • 为什么这里出现重复 Build?

这些问题,都需要理解底层机制才能快速定位。


本章总结

这一章,我们没有分析官方源码,而是手写了一个简化版响应式框架,理解了:

✅ Observable 如何监听属性变化
✅ Observer 如何通知订阅者
✅ Dependency 如何收集依赖
✅ Watcher 如何触发组件更新
✅ Scheduler 如何合并多次刷新
✅ Build、Diff、RenderNode 的完整执行链路
@Observed@ObjectLink 的设计思想

至此,你已经具备了从框架原理角度理解 HarmonyOS ArkUI 状态管理的能力。

第十六章 HarmonyOS 状态管理性能优化与百万级数据渲染实战(企业级性能篇)

前面十五章,我们已经彻底理解了:

  • @State
  • @Observed
  • @ObjectLink
  • Diff
  • Build
  • Dirty
  • 渲染机制

但是,真正进入企业项目后,新的问题出现了:


为什么页面越来越卡?

为什么一个按钮点击以后Build几十次?

为什么首页滚动掉帧?

为什么聊天列表越来越慢?

为什么CPU一直很高?

这些问题,本质上已经不是状态管理是否正确,而是状态管理是否高效

这一章将从 HarmonyOS 企业项目性能优化角度,系统讲解 ArkUI 的优化思路。


16.1 企业项目最大的性能问题是什么?

很多开发者认为:


图片太大

网络太慢

动画太多

实际上,在企业项目中,更常见的问题是:

状态设计不合理,导致大量无效渲染。

例如:


GoodsPage
│
├── Banner
├── GoodsInfo
├── Comment
├── Recommend
├── BottomBar

如果:


goods.favorite = true

结果:


GoodsPage

↓

全部重新Build

那么:

即使页面只有一个收藏按钮变化,也会导致整个页面重新计算。


16.2 Build 次数为什么越来越多?

来看一个错误示例:


@Component
struct GoodsPage {

    @State
    goods: Goods = new Goods()

    build() {
        Column() {
            GoodsBanner({ goods: this.goods })
            GoodsInfo({ goods: this.goods })
            GoodsComment({ goods: this.goods })
            GoodsBottom({ goods: this.goods })
        }
    }
}

所有组件:

都依赖:


goods

修改:


goods.favorite = true

可能导致多个子组件重新参与 Build。

虽然 Diff 会尽量减少最终渲染,但 Build 本身也有成本


16.3 正确拆分状态

推荐:


Goods
├── BaseInfo
├── PriceInfo
├── CommentInfo
├── SpecInfo
└── FavoriteInfo

组件:


GoodsInfo
    ↓
BaseInfo

GoodsPrice
    ↓
PriceInfo

FavoriteButton
    ↓
FavoriteInfo

这样:

修改:


favoriteInfo.isFavorite = true

影响:


FavoriteButton

而不是:

整个页面。


16.4 不要让一个 @State 管理所有数据

错误:


@State
pageData = {
    loading: false,
    dialogVisible: false,
    user: new User(),
    goods: new Goods(),
    order: new Order()
}

问题:

任何一个字段变化,都会增加页面状态的复杂度。

推荐:


@State loading = false

@State dialogVisible = false

@State currentTab = 0

业务对象:

使用:


@Observed

分别管理。


16.5 列表为什么越来越卡?

例如:

商品列表:


1000条商品

很多人:


ForEach(goodsList, ...)

每一个 Item:

里面:


Column() {
    ...
}

然后:

点击:

收藏:


goods.favorite = true

如果 Item 没拆组件。

可能:

整个:

List:

重新参与 Build。


推荐:


GoodsList

↓

GoodsItem

↓

GoodsPrice

↓

FavoriteButton

最小:

刷新。


16.6 LazyForEach 为什么性能更高?

普通:


ForEach()

假设:

10000条数据。

第一次:

全部:

创建。


10000 RenderNode

内存:

暴涨。


LazyForEach:

第一次:

只创建:


可视区域

例如:


20条

滑动:

继续:

创建。

因此:


首次加载更快

内存更低

滚动更流畅

注意:LazyForEach 并不会减少单个 Item 的 Build 成本,它优化的是创建时机和内存占用


16.7 不要频繁 new Model

错误:


this.goods = new Goods(data)

如果:

每秒:

执行:


10次

意味着:


Observer

重新建立

ObjectLink

重新绑定

依赖重新收集

推荐:

修改:


this.goods.price = data.price
this.goods.stock = data.stock

保留:

对象引用。


16.8 图片列表优化

例如:

电商首页:


Banner

↓

1000商品

↓

图片

推荐:

不要:

每次:

Build:

都:


Image(item.url)

应该:

结合:

图片缓存。

避免:

重复:

解码。


16.9 网络请求不要直接改 UI

错误:


async load() {

    let data = await api()

    this.goods = data

}

推荐:


Api

↓

Repository

↓

ViewModel

↓

Observed Model

↓

UI

这样:

UI:

始终:

依赖:

Model。

网络:

负责:

更新:

Model。


16.10 为什么不要频繁切换整个 List?

错误:


this.goodsList = newList

如果:

1000条。

Diff:

需要:

重新比较。

推荐:

增量:

更新。

例如:


goodsList[index].price = 99

或者:


goodsList.push(item)
goodsList.splice(...)

利用稳定 Key,让框架复用已有节点。


16.11 如何减少无效 Build?

企业项目建议:

原则一:组件尽量小

不要:


OrderPage

3000行

推荐:


OrderPage

├── Header
├── OrderInfo
├── GoodsList
├── Address
├── Footer

原则二:状态最小化

不要:


@State appState

推荐:


@State loading

@State tabIndex

原则三:业务对象拆分

例如:


User

↓

BaseInfo

↓

Wallet

↓

Permission

而不是:


User

200字段

原则四:列表 Item 独立组件

例如:


MessageList

↓

MessageItem

不要:

所有:

代码:

写:

一个:

ForEach。


16.12 企业级聊天列表优化

IM:


50000消息

推荐:


ChatPage

↓

LazyForEach

↓

MessageItem

↓

MessageContent

每条消息:

独立:


ObjectLink

修改:


message.progress = 80

刷新:

只有:

这一条。


16.13 企业级直播间优化

直播:

页面:


直播画面

↓

聊天室

↓

礼物

↓

排行榜

↓

公告

↓

在线人数

不要:

全部:

放:

一个:

State。

推荐:


LiveState

ChatState

GiftState

RankState

互不影响。


16.14 如何定位性能问题?

推荐排查顺序:

  1. Build 是否过于频繁?
  2. 组件是否过大?
  3. 是否存在无意义的对象替换?
  4. 列表是否使用稳定 Key?
  5. 是否应该使用 LazyForEach
  6. 是否把无关状态放在同一个对象中?
  7. 是否存在重复网络请求导致重复状态更新?

对于大型项目,可以结合 DevEco Studio 的性能分析工具,观察组件重建、CPU、内存和帧率表现,而不是只凭感觉优化。


16.15 HarmonyOS 状态管理性能优化清单(30 条精选)

数据模型

  • 一个业务一个 Model
  • 不要把所有数据放一个对象
  • @Observed 只用于需要响应更新的对象
  • 保持对象引用稳定,避免无意义 new

页面设计

  • 页面只保存页面状态
  • ViewModel 管理业务逻辑
  • Repository 管理数据来源

列表优化

  • 使用稳定 Key
  • Item 独立组件
  • 超长列表优先 LazyForEach
  • 避免频繁整体替换数组

组件设计

  • 一个组件一个职责
  • 缩小依赖范围
  • 不要为了方便把整个根对象一路传到底
  • 必要时使用 @ObjectLink 共享最小业务对象

更新策略

  • 优先修改属性
  • 避免重复赋值相同数据
  • 合理合并状态更新
  • 避免在高频事件中执行复杂计算

本章总结

这一章我们从企业级性能角度分析了 ArkUI:

✅ 为什么 Build 次数会越来越多
✅ 如何拆分状态,减少无效刷新
✅ 为什么 LazyForEach 更适合大型列表
✅ 如何设计聊天、商城、直播等高性能页面
✅ 如何定位状态管理导致的性能问题
✅ 企业项目状态管理优化最佳实践

到这里,你已经不仅能够正确使用 @Observed@ObjectLink,也具备了设计高性能 HarmonyOS 状态架构的能力。

第十七章 HarmonyOS @Observed + @ObjectLink 企业级实战项目(完整商城 App 架构)

经过前面十六章,我们已经掌握了:

  • @State
  • @Observed
  • @ObjectLink
  • @Link
  • @Provide/@Consume
  • MVVM
  • Repository
  • Build
  • Diff
  • 性能优化

但是这些知识都是零散的。

本章开始,我们通过一个企业级商城项目,把前面的所有知识串联起来。

本章采用真实企业项目的组织方式,而不是 Demo 式代码。


17.1 项目架构

假设开发一个商城 App,包括:


首页
商品分类
商品详情
购物车
订单
我的
搜索
客服聊天
消息中心

整个工程目录推荐如下:


entry
│
├── pages
│   ├── HomePage
│   ├── GoodsPage
│   ├── CartPage
│   ├── OrderPage
│   ├── MinePage
│
├── components
│   ├── GoodsCard
│   ├── GoodsPrice
│   ├── GoodsBanner
│   ├── CartItem
│   ├── UserInfo
│
├── model
│   ├── Goods.ts
│   ├── Cart.ts
│   ├── User.ts
│   ├── Order.ts
│
├── viewmodel
│   ├── GoodsViewModel.ts
│   ├── CartViewModel.ts
│   ├── UserViewModel.ts
│
├── repository
│   ├── GoodsRepository.ts
│   ├── UserRepository.ts
│
├── network
│   ├── Http.ts
│
└── utils

大型项目中,不要把 Model、网络、页面全部写在一个目录


17.2 Model 层设计

商品对象:


@Observed
export class Goods {

    id: string = ""

    title: string = ""

    price: number = 0

    originPrice: number = 0

    stock: number = 0

    favorite: boolean = false

    sales: number = 0

    cover: string = ""

}

这里每一个商品:

都是一个:


@Observed Object

后续:

所有组件共享。


17.3 ViewModel 设计

例如:


export class GoodsViewModel {

    goodsList: Goods[] = []

    loading: boolean = false

    async loadGoods() {

        this.loading = true

        let list = await GoodsRepository.loadGoods()

        this.goodsList = list

        this.loading = false

    }

}

注意:

这里:

页面:

不知道:

网络。

不知道:

数据库。

只知道:


ViewModel

17.4 Repository

统一:

管理:

数据。


class GoodsRepository {

    static async loadGoods() {

        let result = await Api.getGoods()

        return result.map(item => {

            let goods = new Goods()

            goods.id = item.id

            goods.title = item.title

            goods.price = item.price

            goods.cover = item.cover

            return goods

        })

    }

}

注意:

这里:

JSON:

转换:

Observed Model。

这是企业项目:

必须做的事情。


17.5 首页设计

页面:


HomePage

包含:


Banner

分类

活动

商品列表

推荐

猜你喜欢

不要:

一个:

组件。

推荐:


HomePage

│

├──Banner

├──Category

├──Recommend

├──GoodsList

└──GuessLike

每个:

自己:

维护:

自己的状态。


17.6 商品列表

商品:

1000条。

推荐:


LazyForEach(
    goodsList,
    (item) => {
        GoodsCard({
            goods: item
        })
    },
    item => item.id
)

注意:

Key:

必须:

稳定。

不要:


index

17.7 商品卡片

组件:


@Component
struct GoodsCard {

    @ObjectLink
    goods: Goods

}

因为:

多个组件:

共享:

同一个:

Goods。

例如:


GoodsCard

↓

GoodsImage

↓

GoodsPrice

↓

FavoriteButton

都是:

同一个对象。


17.8 收藏按钮

按钮:


Image(
this.goods.favorite
?
$favorite
:
$unFavorite
)

点击:


.onClick(()=>{

this.goods.favorite=!this.goods.favorite

})

这里只:

修改:


favorite

无需:

通知:

父页面。

无需:

刷新:

整个:

List。


17.9 商品详情

页面:


GoodsPage

↓

Banner

↓

Price

↓

Title

↓

Spec

↓

Comment

↓

BottomBar

全部:

共享:


Goods

因此:

全部:

使用:


@ObjectLink

例如:

Price:


@Component
struct GoodsPrice{

@ObjectLink
goods:Goods

}

17.10 购物车

购物车:

推荐:


Cart

↓

CartItem

↓

Goods

不要:

复制:

Goods。

而是:


CartItem

↓

引用:

Goods

这样:

商品:

价格:

变化。

购物车:

自动:

刷新。


17.11 用户中心

用户:


@Observed
class User{

nickname=""

avatar=""

vip=false

balance=0

}

多个页面:

共享:

例如:


Mine

↓

Header

↓

Wallet

↓

Vip

↓

Order

全部:


ObjectLink

共享。


17.12 WebSocket 更新

后台:

推送:


{
"goodsId":1001,
"price":88
}

Repository:

找到:


goods.price=88

结束。

页面:

自动:

刷新。

整个:

流程:


WebSocket

↓

Repository

↓

Observed Goods

↓

ObjectLink

↓

PriceComponent

无需:

手动:

通知。


17.13 登录状态

推荐:

App:

启动:


AppState

↓

UserState

↓

CurrentUser

页面:

使用:


Provide

Consume

或者:

统一:

状态中心。

不要:

每页:

请求:

用户信息。


17.14 搜索页面

搜索:

不要:


全部刷新

推荐:


SearchPage

↓

SearchInput

↓

History

↓

Suggestion

↓

GoodsResult

输入:

变化。

只:

更新:

Suggestion。

不是:

整个页面。


17.15 IM 客服

消息:


Message

↓

MessageItem

↓

MessageContent

发送:


message.progress=60

刷新:

这一条。

不会:

整个:

聊天记录。


17.16 企业项目状态流

整个商城:

推荐:

统一:

数据流。


用户点击

↓

ViewModel

↓

Repository

↓

Network

↓

Model(@Observed)

↓

ObjectLink

↓

Component

↓

Build

↓

Diff

↓

Render

所有:

页面:

一致。


17.17 企业开发规范(建议)

1. 页面不要直接请求网络

错误:


HomePage

↓

Http.get()

正确:


HomePage

↓

ViewModel

↓

Repository

↓

Api

2. 不要直接使用 JSON

统一:

转换:


JSON

↓

Observed Model

3. 一个组件一个职责

错误:


GoodsPage

5000行

正确:


GoodsPage

↓

GoodsInfo

↓

GoodsPrice

↓

GoodsComment

↓

BottomBar

4. 状态最小化

不要:


AppState

100字段

推荐:


GoodsState

UserState

OrderState

模块化。


17.18 企业项目最容易犯的错误(Top 10)

错误 后果 推荐做法
页面直接请求接口 页面职责混乱 使用 ViewModel + Repository
直接使用 JSON 无法响应更新 转换为 @Observed Model
整页只有一个 @State 无效 Build 多 拆分状态
整个对象重新 new 丢失引用 修改对象属性
ForEach 使用 index 列表错乱 使用稳定业务 ID
超长列表使用 ForEach 内存高 使用 LazyForEach
大组件 3000+ 行 难维护 小组件拆分
一个 Model 几百字段 更新范围过大 按业务拆分 Model
@ObjectLink 重新赋值 响应链断开 修改属性,不替换对象
页面共享数据靠复制 数据不同步 引用共享

本章总结

本章通过一个企业级商城项目,把前面十六章的知识串联起来,完成了:

✅ 工程目录规划
✅ MVVM + Repository 架构
@Observed Model 设计
@ObjectLink 组件共享
✅ 商品列表、购物车、用户中心、搜索、IM 等真实场景落地
✅ 企业开发规范与十大常见错误

到这里,你已经具备了使用 @Observed@ObjectLink 设计中大型 HarmonyOS 应用的能力。

第十八章 HarmonyOS @Observed + @ObjectLink 面试专题(100 道高频面试题详解)

经过前面十七章,我们已经从使用方法、底层原理、企业架构到性能优化进行了完整讲解。

本章将围绕 HarmonyOS 状态管理,整理企业招聘中最常见的面试题,并结合原理进行详细解析。

适用对象:

  • HarmonyOS 初级开发工程师
  • HarmonyOS 高级开发工程师
  • HarmonyOS 架构师
  • 鸿蒙技术负责人

第一部分 基础篇(1~20题)


1、什么是 @Observed?

参考答案:

@Observed 用于声明一个可观察对象(Observable Object)

当对象内部的可观察属性发生变化时,依赖该对象的组件可以收到通知并重新参与 Build。

例如:


@Observed
class User {
  name: string = ""
  age: number = 0
}

注意:

@Observed 修饰的是,而不是组件。


2、@Observed 和 @State 有什么区别?

很多人回答:

一个修饰对象,一个修饰变量。

这个回答太浅。

更完整的回答:

@State @Observed
页面局部状态 可观察业务对象
属于组件 属于 Model
生命周期跟随组件 生命周期跟随对象
保存页面状态 保存业务数据

一句话总结:

@State 管理页面状态,@Observed 管理业务对象。


3、为什么 @Observed 需要配合 @ObjectLink?

因为:


Observed

负责:

监听变化

而:


ObjectLink

负责:

建立引用关系

职责完全不同。

可以理解成:


Observed

负责:

广播

↓

ObjectLink

负责:

接收广播

缺一不可。


4、ObjectLink 为什么不能直接 new 一个对象?

例如:

错误:


this.user = new User()

原因:

@ObjectLink 建立的是:


父组件

↓

对象引用

↓

子组件

如果:

子组件:

自己:

new。

引用:

就断开了。

因此:

不要:

重新赋值。

推荐:

修改:


this.user.name="Tom"

5、ObjectLink 和 Link 的区别?

很多人:

只回答:

对象。

实际上:

区别:

Link ObjectLink
双向绑定 引用共享
普通类型 Observed对象
变量同步 对象同步
Boolean/String Model

6、Prop 为什么不能修改?

因为:

Prop:

表示:


父

↓

子

单向数据流。

数据所有权:

属于:

父组件。


7、Provide 和 Consume 是全局变量吗?

不是。

它们:

属于:

当前:

组件树。

页面销毁。

Provide:

也:

销毁。

真正:

全局:

应该:

使用:

AppStorage:

或者:

统一:

状态中心。


8、为什么 Observed 修改对象属性会刷新?

因为:

框架:

监听:

Setter。

例如:


user.name="Jack"

内部:

实际上:

执行:


Setter

↓

Dependency

↓

Watcher

↓

Build

9、为什么直接替换对象有时候不刷新?

例如:


user = new User()

可能:

新的对象:

没有:

原来的:

观察关系。

所以:

推荐:

修改:

属性。


10、ForEach 为什么必须使用稳定 Key?

因为:

Diff:

需要:

知道:

是不是:

同一个:

节点。

错误:


index

正确:


item.id

第二部分 原理篇(21~50题)


21、ArkUI 是如何知道对象发生变化的?

参考答案:

ArkUI 会为 @Observed 管理的对象建立可观察关系。

当对象属性发生变化时,会通知依赖组件,并将组件标记为待更新,随后统一参与 Build 和 Diff。


22、什么是 Dirty?

Dirty:

表示:


需要:

重新Build

不是:

立即:

刷新。

而是:

等待:

下一帧:

统一:

执行。


23、为什么不能每次修改立即刷新?

例如:


user.name="Tom"

user.age=20

user.phone="123"

立即:

刷新:


Build

Build

Build

性能:

很差。

所以:

统一:

Scheduler。


24、什么是 Scheduler?

Scheduler:

负责:


收集

Dirty

↓

统一

Build

减少:

重复:

刷新。


25、Build 和 Render 有什么区别?

Build:

生成:


UI Tree

Render:

真正:

更新:

RenderNode。

很多人:

容易:

混淆。


26、ArkUI 有没有 Virtual DOM?

没有。

ArkUI:

直接:

生成:

RenderNode。

因此:

流程:

更加:

简单。


27、ArkUI Diff 比较什么?

比较:

新旧:

UI Tree。

找到:

变化:

节点。

不是:

全部:

重新:

创建。


28、为什么组件拆分越细性能越高?

因为:

依赖:

更精准。

例如:


Price

只:

依赖:

price

修改:

标题。

Price:

不用:

Build。


29、Builder 为什么容易导致状态问题?

因为:

@Builder:

不是:

独立:

Component。

没有:

完整:

生命周期。


30、为什么大型对象不要放 State?

例如:


AppData

500字段

任何:

变化。

都:

可能:

影响:

页面。


第三部分 企业项目篇(51~80题)


51、企业项目推荐什么架构?

推荐:


UI

↓

ViewModel

↓

Repository

↓

DataSource

不要:

页面:

直接:

请求。


52、Repository 有什么作用?

统一:

管理:

数据来源。

包括:


网络

数据库

缓存

WebSocket

页面:

无需:

关心。


53、为什么 JSON 要转换 Model?

JSON:

只是:

普通对象。

没有:

观察能力。

推荐:

转换:


JSON

↓

Observed Model

54、为什么 ViewModel 不直接写 UI?

职责:

分离。

方便:

测试。

方便:

维护。


55、聊天列表为什么推荐 ObjectLink?

因为:

消息:

变化:

只:

刷新:

当前:

Message。


56、直播为什么拆多个 State?

例如:


聊天室

礼物

排行榜

公告

互不:

影响。


57、为什么不要频繁 new Model?

原因:

重新:

建立:

Observer。

重新:

Dependency。

重新:

ObjectLink。

成本:

高。


58、LazyForEach 为什么快?

因为:

不是:

一次:

创建:

10000:

Item。

而是:

按需:

创建。


59、ObjectLink 为什么适合商城?

因为:

商品:

多个:

组件:

共享。

例如:


Price

↓

Favorite

↓

Stock

全部:

引用:

Goods。


60、为什么状态最小化?

减少:

无效:

Build。


第四部分 高级架构篇(81~100题)


81、为什么 HarmonyOS 不采用 React Fiber?

可以这样回答:

React Fiber 是为浏览器和 Virtual DOM 设计的调度架构。

ArkUI 面向的是原生渲染树,因此采用了适合自身渲染模型的状态驱动更新机制,而不是直接照搬 Fiber。


82、HarmonyOS 更像 Vue 还是 Flutter?

推荐回答:

状态管理思想:

接近:

Vue。

渲染流程:

接近:

Flutter。


83、ObjectLink 为什么适合 MVVM?

因为:

ViewModel:

维护:

Observed Model。

组件:

共享:

ObjectLink。

数据:

天然:

一致。


84、为什么 Build 不等于 Render?

Build:

只是:

生成:

UI。

Render:

才:

真正:

绘制。


85、HarmonyOS 状态更新最大的性能瓶颈是什么?

不是:

Diff。

通常:

是:

无效 Build

例如:

整个:

首页:

Build。

只是:

因为:

一个:

按钮。


86、如何减少 Build?

  • 拆组件
  • 拆状态
  • 最小依赖
  • ObjectLink
  • LazyForEach

87、如果首页有1000商品怎么办?

推荐:


LazyForEach

↓

GoodsItem

↓

ObjectLink

不要:

全部:

State。


88、Observed 为什么不能监听普通 JSON?

因为:

普通:

Object:

没有:

观察机制。


89、为什么大型项目推荐 Repository?

方便:

以后:

增加:

缓存:

数据库:

离线:

同步。

页面:

不用:

修改。


90、HarmonyOS 状态管理设计思想是什么?

一句话:

数据驱动 UI,依赖驱动刷新。


第五部分 面试压轴题(91~100)


91、如果让你设计一个 HarmonyOS 状态管理框架,你会怎么做?

建议回答思路:


Observable

↓

Dependency

↓

Watcher

↓

Scheduler

↓

Build

↓

Diff

↓

RenderNode

说明每一层的职责,并强调依赖收集批量调度的重要性。


92、如何设计一个百万级用户商城的状态架构?

回答关键词:

  • MVVM
  • Repository
  • Model 分层
  • 页面状态与业务状态分离
  • 模块化 State
  • 最小依赖更新
  • LazyForEach
  • WebSocket 增量更新

93、如何定位页面频繁刷新的原因?

建议排查:

  1. 是否存在重复赋值?
  2. 是否替换了整个对象?
  3. 是否组件过大?
  4. 是否状态设计过于集中?
  5. 是否列表 Key 不稳定?

94、如何保证多个页面数据一致?

推荐:


Repository

↓

共享 Model

↓

ObjectLink

↓

统一更新

而不是:

多个页面:

各自:

维护:

一份数据。


95、如何处理服务器推送的数据?

推荐流程:


WebSocket

↓

Repository

↓

更新 Observed Model

↓

UI 自动刷新

避免:

页面:

直接:

监听:

Socket。


96、为什么企业项目强调单向数据流?

因为:

数据来源清晰。

容易:

定位:

Bug。

便于:

测试。


97、如果页面越来越卡,你会怎么优化?

回答顺序:

  • 查看 Build 次数
  • 查看组件拆分
  • 查看状态粒度
  • 查看列表渲染
  • 查看对象是否频繁创建
  • 查看图片与网络是否阻塞主线程

98、如何理解“数据驱动 UI”?

UI:

只是:

状态:

的一种映射。

不要:

主动:

操作:

控件。

而是:

修改:

数据。


99、HarmonyOS 状态管理最大的优势是什么?

可以概括为三点:

  • 响应式状态管理
  • 精细化依赖更新
  • 原生渲染能力

100、一句话总结 @Observed@ObjectLink

@Observed 负责让对象具备响应能力,@ObjectLink 负责让多个组件共享同一个响应式对象。二者配合,实现数据驱动 UI 的高效更新。

Logo

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

更多推荐