说明
这部分资料是基于 3.0/4.0的文档。后续如有更新,请注意查看最新版本
状态变量到组件更新的原理
自定义组件的创建和渲染流程 章节里有如下的描述,非常有用
总结一下
只要状态变量被读取,就会保存两个map,一个是状态变量到组件,一个是组件到该组件的更新函数。
那么状态变量变化时的流程就清晰了,首先通过状态变量找到组件,然后通过组件找到该组件的更新函数,执行一次

有状态变量的组件的更新函数
- 从上面的创建和渲染图里,我们看到了
observeComponentCreation - 这个函数将自定义组件的创建包裹起来,其中自然也包括自定义组件的build函数。这样,当状态变量变化时,调用
observeComponentCreation即可完成组件的重新创建和刷新 - 我们把arkui_ace_engine代码下载下来,搜索
observeComponentCreation,发现搜索结果还是挺多的。看到一个熟悉的pu_view.ts,咱们看一下

observeComponentCreation 观察组件创建
- 我们看到有两个观察组件创建的函数,一个是
observeComponentCreation,另一个是observeComponentCreation2 observeComponentCreation2是2023年7月新加的,用于替换observeComponentCreation。咱们继续搜索observeComponentCreation2
observeComponentCreation2 观察组件创建
搜索 observeComponentCreation2,我们发现函数定义的地方只有两个,一个是构建节点的类 builder_node.ts 一个是部分更新的类 pu_view.ts 
builder_node.ts UI节点
在这里我们看到了之前提到的一个Map以及刷新函数。
同时我们也看到它是通过new Map创建的

pu_view.ts UI部分更新
这个类里面的处理和上面的几乎一样,不多赘述 
搜索= new Map()
为什么搜索= new Map(),是因为一共两个Map。咱们找一下第一个Map在哪里。我们查看mgmtState.js这个类里的结果
只在本类中搜索= new Map(),我们发现了class LocalStorage、class SubscriberManager、class PersistentStorage、class Environment、class View、class ViewPU、class ObservedPropertyAbstractPU、class SynchedPropertyOneWayPU、class RecycleManager等多个类
经过筛选,我们发现了一个有趣的东西, this.trackedObjectPropertyDependencies_ = new Map();。它本身是一个内部类的一个方法,我们看一下这个内部类

搜索 this.trackedObjectPropertyDependencies_.set
看一下set方法,也就是存储kv的地方在哪,存了什么东西。
可以看到实际上key为状态变量,value则是组件的集合Set 
搜索 addTrackedObjectPropertyDependency
搜索一下调用set的地方有哪些入口 
搜索 this.recordTrackObjectPropertyDependencyForElmtId
继续搜索下面方法的调用,我们找到四处结果,都是 onOptimisedObjectPropertyRead(readObservedObject, readPropertyName, isTracked)内部调用的 
- @ObjectLink状态变量的实现

- @State和@Provide 状态变量的实现

- 单向同步的状态变量实现,主要指@Prop系列

- 双向同步的状态变量实现,@Link和@Consume

搜索 .onOptimisedObjectPropertyRead
出现了四个和上面对应的单独ts文件。
先不用管,咱们继续看 stateMgmt.js这个类中 this.onOptimisedObjectPropertyRead 的调用,我们选取一个查看
class ObservedPropertyPU get
可以看到当状态变量的get方法被使用时,添加kv映射。当状态变量的set方法被使用时,通过kv之后找到组件进行更新渲染
ObservedObject.registerPropertyReadCb
ObservedObject这个类也在stateMgmt.js里面,所以直接在本类中搜索就行 
SubscribableHandler.SET_ONREAD_CB
可以看到它是通过Symbol来实现的,保证独一无二 
this.wrappedValue_ 哪来的
看一下咱们上面截图的set方法,它调用了一个setValueInternal,ok看一下这个方法
可以看到分三种情况,我们目前只关心1和3 一、本身已经是一个可观察对象,比如@Observed装饰的class
只是将组件放到了可观察对象的一个属性上,属性name也是一个Symbol 
三、本身不是可观察对象。比如基础值
把基础值包装成了一个可观察对象 
查看 class ObservedPropertyPU set 方法

查看 this.notifyPropertyHasChangedPU.方法
可以看到,先同步状态变量,之后再通知@Watch订阅的回调 
查看 this.notifyTrackedObjectPropertyHasChanged 方法
可以看到,调用了viewPropertyHasChanged并将所有依赖这个状态变量的组件传进去了 
查看 viewPropertyHasChanged
可以看到将需要更新的组件都添加到了 dirtDescendantElementIds_ 中
我们找下 dirtDescendantElementIds_ 的 forEach或者for...of调用
通过代码我们知道 父组件肯定在子组件前更新
我们再搜索 updateDirtyElements,发现 stateMgmt里面只有重用的时候调用了,但是其他文件里的rerender里都是调用的updateDirtyElements

总结一下
-
我们声明的每一个状态变量,会被包装成一个ObservedObject,这个类里面重写
get set方法 -
get方法:我们访问这个状态变量时,将存储状态变量到组件集合这个kv存储下来 -
set方法:状态变量改变时会做以下步骤a. 先更新关联的状态变量。比如@State变化,先更新关联的@Prop或@Link状态变量
b. 通过map获取到依赖的组件,将组件标记为dirty。等待下次渲染更新UI
-
子组件的状态变量,通过第三步的a操作也发生变化,之后就和上面三步是一样的了。可以看出这是一个递归的操作过程
如果您仔细看了状态管理篇章 @Link装饰器:父子双向同步。您会发现和里面介绍的一模一样。咱们只是从代码层面验证了一遍
好了,感谢您的阅读。我写的比较浅显,如果您有精力还可以研究的更深,还有更多小细节。