【HarmonyOS】多层嵌套对象通过 @ObjectLink 和 @Observed 实现渲染更新处理(超详细实战,)
适用版本:
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
✅ push、splice、sort 为什么能触发列表更新
✅ 为什么稳定的 Key 对性能至关重要
✅ 聊天、商城、通讯录等企业级列表最佳实践
第十章 @Observed + @ObjectLink 底层实现原理(ArkUI 渲染机制深度解析)
前面几章,我们已经能够熟练使用:
@State@Observed@ObjectLinkForEach- 多层对象
- 多层数组
但是很多人仍然有一个疑问:
为什么修改一个属性,
页面就知道刷新了?
它到底是怎么知道哪个组件需要更新?
为什么不是整个页面重新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 企业项目如何减少无效刷新?
很多团队在项目初期会把所有数据都放到一个巨大的 User 或 AppState 中,结果一个小字段变化,很多组件都依赖它,维护成本越来越高。
推荐的做法是:
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 如何定位性能问题?
推荐排查顺序:
- Build 是否过于频繁?
- 组件是否过大?
- 是否存在无意义的对象替换?
- 列表是否使用稳定 Key?
- 是否应该使用
LazyForEach? - 是否把无关状态放在同一个对象中?
- 是否存在重复网络请求导致重复状态更新?
对于大型项目,可以结合 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、如何定位页面频繁刷新的原因?
建议排查:
- 是否存在重复赋值?
- 是否替换了整个对象?
- 是否组件过大?
- 是否状态设计过于集中?
- 是否列表 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 的高效更新。
更多推荐



所有评论(0)