说明
这部分资料是基于 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装饰器:父子双向同步。您会发现和里面介绍的一模一样。咱们只是从代码层面验证了一遍
好了,感谢您的阅读。我写的比较浅显,如果您有精力还可以研究的更深,还有更多小细节。