HarmonyOS5 源码分析 —— ‘状态管理’如何管理的(1)?

一、前言

大家都是开发,有时候写着写着 ArkUI 代码,突然就会冒出个问号:

"为啥我改个状态变量,UI 就自动刷新了?"

"@Local 到底背后做了啥?能监听到那么精准吗?"

"那 View 更新的时机呢?是 Diff 算法还是真全量更新?"

于是,就顺手点开了 openharmony 的源码,想看看 Local 管理到底是怎么实现的。

一翻不要紧,直接翻到了个叫 StateMgmt 的模块,感觉挺有门道,也挺值得写点什么出来。于是就有了这篇文章(也可能是第一篇,谁知道会不会越写越多呢 )。

因为用V2很久了,所以这次也会用V2的状态管理作为示例。如果想了解V1、V2的差别,V2诞生的背景,请务必点赞收藏分享~

如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,如果你想支持下一期请务必点赞~,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏

二、关于状态管理

我们都知道,ArkUI 的核心就是响应式 ------ 改个变量,UI 就能自动更新,不用你手动 setState()、不用你手动通知。

而实现这一点的底层机制,正式的名字叫做 状态管理(State Management)系统

什么是状态管理?

状态管理,说人话就是:

"你改的数据,我怎么知道该刷新哪个 UI?"

在 ArkUI 中,开发者习惯于写这样的代码:

scss 复制代码
@Local count = 0;

build() {
  Text(`点击次数:${this.count}`);
}

你只管改 count++,UI 就自己刷新了。整个过程你不需要管"谁依赖谁""刷新在哪发生的"。这背后,其实就是 状态和 UI 的绑定系统 在默默发力。

而这个"绑定系统"的大本营,就在 OpenHarmony 的这个路径下:

bash 复制代码
/frameworks/bridge/declarative_frontend/state_mgmt/

在使用V1的时候,大家也应该被@State,@Link等V1的状态装饰器给恶心过,以及各种嵌套数据和UI的使用不便。种种原因都导致了V2的出现。

整个状态管理模块结构大致如下:

ts 复制代码
state_mgmt/
├── v2/              # 状态管理 V2 的主力实现(你该重点关注的)
├── full_update/     # 全量更新逻辑
└── common/          # 通用工具类、抽象接口
  • v2/:所有和现代响应式相关的逻辑都在这,包括 Proxy、装饰器、依赖追踪、UI更新调度等。
  • full_update/:主打兼容和简单项目用。
  • common/:工具类、抽象类、日志追踪等通用逻辑。

下面是其中几个核心文件:

文件 作用
v2_view.ts UI 节点与状态的绑定逻辑,谁改了要刷谁,就看它
v2_observed_proxy.ts Proxy 实现,拦截 get/set 实现依赖追踪和变更通知
v2_change_observation.ts 依赖管理器,谁依赖谁、谁变了要通知谁,全靠它调度
v2_decorators.ts / v2_decorated_variables.ts 定义响应式装饰器,比如 @Observed@Param
v2_computed.ts 实现 @Computed 计算属性
v2_monitor.ts 实现 @Monitor 等装饰器,支持路径监听
v2_json_coder.ts / v2_make_observed.ts 一些数据转换和对象代理辅助方法

三、响应式系统的运行机制

"我只是加了个装饰器,UI 怎么就知道我改了值?"(系统猜的0.0)

从你声明变量的那一刻起,它就被盯上了 (实际上是编译完才被盯上)。

看看大致流程:TODO

  • 声明变量(@Local)
  • 收集依赖(build 时读取变量)
  • 通知刷新(变量值变化时触发 UI 更新)

下面我们就按这流程,一步步扒源码

四、再细一点

装饰器实现

展开说说装饰器吧,先说 @ObservedV2@Trace 是怎么把"一个普通变量"变成"一个响应式变量"的。

核心思路:

  • 在组件定义阶段,系统会扫描属性上的装饰器。
  • 对于被 @ObservedV2 装饰的属性,会注册元信息。
  • 然后重写这个属性,挂上 getter/setter,让它支持依赖追踪和通知更新。

@Trace 为例,回顾刚刚的代码吗:

ts 复制代码
const Trace = (target: Object, propertyKey: string): void => {
  // 把这个属性登记为"响应式属性"
  ObserveV2.addVariableDecoMeta(target, propertyKey, '@Trace');
  // 把原来的属性"改造"成响应式变量
  return trackInternal(target, propertyKey);
};

trackInternal() 做的事,就是用 Reflect.defineProperty,把属性换成这样:

ts 复制代码
Reflect.defineProperty(target, propertyKey, {
  get() {
    ObserveV2.getObserve().addRef(this, propertyKey); // 依赖收集
    return this[`__$${propertyKey}`];
  },
  set(val) {
    this[`__$${propertyKey}`] = val;
    ObserveV2.getObserve().fireChange(this, propertyKey); // 通知变更
  }
});

这样,一个变量就被包装成了"带钩子的变量"
2.

依赖收集逻辑(ObserveV2 + Proxy

  1. OK,现在变量已经"装饰好了",那它是怎么知道谁依赖它、谁要刷新的?

    全靠 ObserveV2

    依赖收集怎么做的?

    我们知道,组件在 build() 过程中,会读取响应式属性。这个时候 ArkUI 会调用:

    ts 复制代码
    ObserveV2.getObserve().startRecordDependencies(this, elmtId);

    然后你在 build() 里写的:

    ts 复制代码
    Text(this.count);

    就会触发 count 的 getter,getter 又会调用:

    ts 复制代码
    ObserveV2.getObserve().addRef(this, 'count');

    从而把 elmtIdcount 之间的依赖关系记录下来。结束之后,再手动调用:

    ts 复制代码
    ObserveV2.getObserve().stopRecordDependencies();

    整个过程大概可以这么理解:

    ts 复制代码
    // 开始收集
    ObserveV2.startRecordDependencies(this, elmtId);
    
    // 在 build 过程中,系统"偷偷记录"你依赖了哪些变量
    Text(this.count);  // getter → addRef(this, 'count')
    
    // 停止收集
    ObserveV2.stopRecordDependencies();

    最终会得到一个类似这样的结构:

    ts 复制代码
    // 内部结构大致示意(伪代码)
    dependencyMap = {
      'count' => Set( elmtId1, elmtId2 )
    }

状态变更与 UI 刷新

我们现在知道了:

  • 谁在用响应式变量(elmtId ← count);
  • 变量一变,系统知道该通知谁(反向查找);

接下来就是最后一环------刷新 UI。

fireChange 是谁干的?

当你执行:

ts 复制代码
this.count++;

实际上走的是 setter,setter 里面会触发:

ts 复制代码
ObserveV2.getObserve().fireChange(this, 'count');

接下来,就轮到 ViewV2 出场了。

刷新流程:

找到所有依赖了 'count' 的 UI 节点

五、小结

因为篇幅的原因,这篇文章我只大概给大家讲了状态管理 V2 的响应式"主流程":从你写了个 @ObservedV2 开始,到 Proxy 拦截,再到 ObserveV2 记录谁依赖谁,最后通过 ViewV2 把变了的 UI 给刷新了。

这一套流程总结下来就一句话:

你改了谁,系统就只会更新用到"谁"的 UI。

不需要你手动调用刷新、不用你维护订阅关系,也不用你去记住 UI 层更新的细节,整个过程就像"写普通变量"一样丝滑。

当然,很多细节我这篇没展开,比如:

  • 装饰器到底是在哪个阶段注册进去的?如何注册进去的?我们能也一样写这样的装饰器吗?
  • @Computed 那种带缓存逻辑的属性是怎么做到只在依赖变了才重新算?
  • 依赖关系的数据结构怎么维护?怎么避免内存泄漏?
  • 多个属性变了,会批处理更新吗?还是每次都立即刷新?

咕咕咕,如果你感兴趣的话~咕咕咕咕,点个赞吧!

六、总结

[如果有想加入鸿蒙生态的大佬们,快来加入鸿蒙认证吧!初高级证书没获取的,点我!!!!!!!!,我真的很需要求求了,通过立马送美女签名照!]

没了。

如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,如果你想支持下一期请务必点赞~,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏

相关推荐
前端菜鸟日常2 小时前
鸿蒙组件装饰器深度解析:@Component vs @ComponentV2
华为·harmonyos
你的人类朋友5 小时前
❤️‍🔥对过度设计的反思
程序员·架构·设计
鹿鸣天涯6 小时前
鸿蒙OS 系统安全
华为·系统安全·harmonyos
wenzhangli77 小时前
OneCode 3.0 前端架构全面研究
前端·架构
拳打南山敬老院10 小时前
从零构建一个插件系统(六)低代码场景的插件构建思考
javascript·架构
kymjs张涛12 小时前
HarmonyOS Next 全兼容,三端统一的路由跳转方案
harmonyos
DemonAvenger13 小时前
Go语言实现高并发网络爬虫:技术实践与经验分享
网络协议·架构·go
前端世界13 小时前
鸿蒙系统下的动态负载均衡实战:让分布式任务调度更智能
分布式·负载均衡·harmonyos
SmalBox13 小时前
【开篇导览】探索游戏渲染从UnityURP开始
架构