# 20.2:页面刷新机制浅析
笔者在第二章第 1 节介绍的《简单计数器示例》里说过 @State 修饰符的功能,当它修饰的属性值变化时会触发 build()
方法的调用从而实现页面刷新的目的,本节笔者简单介绍一下 @State 修饰符是如何做到页面刷新的。
# 20.2.1:ObservedPropertySimple初识
根据上节对编译后的 abc 和 js 文件分析,我们发现项目打包后 @State 修饰的 string
类型的message
属性被替换成了 ObservedPropertySimple
类型的 __message
属性,后续对 message
属性的修改转而变成了对 __message
属性的操作,ObservedPropertySimple
的源码在 ACE (opens new window) 仓里的 state_mgmt 目录下,如下所示:
class ObservedPropertySimple<T> extends ObservedPropertySimpleAbstract<T> implements ISinglePropertyChangeSubscriber<T> {
// 原始属性的原始值
private wrappedValue_: T;
constructor(value: T, owningView: IPropertySubscriber, propertyName: PropertyInfo) {
super(owningView, propertyName);
// 不支持 object 类型
if (typeof value === "object") {
throw new SyntaxError("ObservedPropertySimple value must not be an object")!
}
// 保存原始值
this.setValueInternal(value);
}
aboutToBeDeleted(unsubscribeMe?: IPropertySubscriber) {
if (unsubscribeMe) {
this.unlinkSuscriber(unsubscribeMe.id__());
}
super.aboutToBeDeleted();
}
// 属性值改变的回调方法
hasChanged(newValue: T): void {
this.notifyHasChanged(this.wrappedValue_);
}
// 更改原始值
private setValueInternal(newValue: T): void {
this.wrappedValue_ = newValue;
}
// 获取属性值
public get(): T {
this.notifyPropertyRead();
return this.wrappedValue_;
}
// 设置属性值
public set(newValue: T): void {
if (this.wrappedValue_ == newValue) {
// 没有变化,则直接返回
return;
}
// 更改属性值
this.setValueInternal(newValue);
// 调用父类方法,触发回调
this.notifyHasChanged(newValue);
}
// 省略部分方法
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
ObservedPropertySimple
继承了 ObservedPropertySimpleAbstract
并实现了 ISinglePropertyChangeSubscriber
接口,它提供的构造方法的参数 value
表示属性的原始值,owningView
表示属性的持有者,propertyName
表示属性的名称,在构造方法内先调用了父类的构造方法,把 owningView
和 propertyName
传递过去,然后调用 setValueInternal()
方法给 wrappedValue_
赋值,当后续给原始属性赋新值时就会调用的它的 set()
方法,该方法先校验新旧值是否相同,如果相同则不做处理,否则先调用 setValueInternal()
方法更新 wrappedValue_
的值,再调用父类的 notifyHasChanged()
方法,该方法是在基类 ObservedPropertyAbstract
中定义的,源码如下所示:
abstract class ObservedPropertyAbstract<T> {
// 保存 IPropertySubscriber 的 id
protected subscribers_: Set<number>
// 当前对象的唯一 id
private id_: number;
// 原始属性名
private info_?: PropertyInfo;
constructor(subscribeMe?: IPropertySubscriber, info?: PropertyInfo) {
// 初始化subscribers_
this.subscribers_ = new Set<number>();
// 给id_赋值唯一值
this.id_ = SubscriberManager.Get().MakeId();
// 把当前ObservedPropertyAbstract对象添加进SubscriberManager内部集合里
SubscriberManager.Get().add(this);
if (subscribeMe) {
// subscribeMe 是 View 类型,保存的是 View 的 id
this.subscribers_.add(subscribeMe.id__());
}
if (info) {
// 保存原始属性名
this.info_ = info;
}
}
// 原始属性值改变时,会调用到该方法
protected notifyHasChanged(newValue: T) {
// 获取SubscriberManager实例
var registry: IPropertySubscriberLookup = SubscriberManager.Get();
// 循环遍历 View 的 id
this.subscribers_.forEach((subscribedId) => {
// 获取缓存的值
var subscriber: IPropertySubscriber = registry!.get(subscribedId)
if (subscriber) {
// 回调相关方法
if ('hasChanged' in subscriber) {
// newValue是更改后的最新值
(subscriber as ISinglePropertyChangeSubscriber<T>).hasChanged(newValue);
}
if ('propertyHasChanged' in subscriber) {
// info_是原始属性的名字
(subscriber as IMultiPropertiesChangeSubscriber).propertyHasChanged(this.info_);
}
} else {
console.error(`ObservedPropertyAbstract[${this.id__()}, '${this.info() || "unknown"}']: notifyHasChanged: unknown subscriber ID '${subscribedId}' error!`);
}
});
}
// 省略部分代码
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
notifyHasChanged()
方法内部循环遍历保存的 View 的 id
值,然后从 SubscriberManager
里边获取对应的 View,当获取到后调用它的 propertyHasChanged()
方法并把属性名字传递过去,那么 View 是在什么时候添加进 SubscriberManager
里边的呢?根据反编译的 abc 文件可知,index
被编译成了继承自 View,View 的源码在 view.ts
中,如下所示:
type ProvidedVarsMap = Map<string, ObservedPropertyAbstract<any>>;
abstract class View extends NativeView implements IMultiPropertiesChangeSubscriber, IMultiPropertiesReadSubscriber {
// 当前 View 的唯一标识
private id_: number;
// 省略部分代码
constructor(compilerAssignedUniqueChildId: string, parent: View, localStorage?: LocalStorage) {
super(compilerAssignedUniqueChildId, parent);
// 省略部分代码
// 创建唯一的 ID
this.id_ = SubscriberManager.Get().MakeId();
// 把当前 View 添加进 SubscriberManager 中
SubscriberManager.Get().add(this);
}
id__(): number {
return this.id_;
}
id() {
return this.id__();
}
abstract aboutToBeDeleted(): void;
abstract updateWithValueParams(params: Object): void;
propertyHasChanged(info?: PropertyInfo): void {
if (info) {
// need to sync container instanceId to switch instanceId in C++ side.
// 数据变化,调用syncInstanceId方法,通知C++
this.syncInstanceId();
if (this.propsUsedForRender.has(info)) {
// 标记当前 View 需要变更
this.markNeedUpdate();
}
let cb = this.watchedProps.get(info)
if (cb) {
cb.call(this, info);
}
// 恢复操作
this.restoreInstanceId();
} // if info avail.
}
// 缓存当前变化的属性值
propertyRead(info?: PropertyInfo): void {
if (info && (info != "unknown") && this.isRenderingInProgress) {
this.propsUsedForRender.add(info);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
由 View 的源码可知,在它的构造方法内调用了 SubscriberManager
的 add()
方法并把自己添加进了 SubscriberManager
的内部集合里,因此在 ObservedPropertyAbstract
的 notifyHasChanged()
方法内会执行到 View 的 propertyHasChanged()
方法,而该方法内部又调用到了父类 NativeView 的 syncInstanceId()
方法和 markNeedUpdate()
方法,目的是通知 C++ 层面当前 View 有值变更需要刷新界面,NativeView 的源码如下所示:
declare class NativeView {
constructor(compilerAssignedUniqueChildId: string, parent: View);
markNeedUpdate(): void;
findChildById(compilerAssignedUniqueChildId: string): View;
syncInstanceId(): void;
restoreInstanceId(): void;
static create(newView: View): void;
}
2
3
4
5
6
7
8
NativeView 定义的方法具体实现是在底层用 C++ 实现的,由于篇幅原因笔者会在后续做详细分析,到目前为止已经清楚了页面的刷新流程,也就是说在 js 层面 @State 修饰的属性值变化了,先执行 ObservedPropertyAbstract
的 notifyHasChanged()
方法,该方法内部找到属性变化所在的 View,调用该 View 的 markNeedUpdate()
方法通知 C++ 层面,该 View 的属性值有变化,C++ 层在接收到信令后做后续的一系列页面刷新逻辑……
# 20.2.2:ObservedPropertySimple类结构
ArkUI 开发框架提供的状态管理使用起来的非常方便,代码量也非常精简,对开发者精简也就意味着 ArkUI 开发框架需要在内部实现相当多的复杂能力,由于篇幅原因,笔者就不在介绍有关状态管理的其它类型实现了,笔者简单梳理了下 state_mgmt 目录下的状态管理类结构图,读者可根据该结构图自己做下其它类型的实现分析。
# 20.2.3:小结
本节笔者简单介绍了 @State 修饰符引发页面刷新的流程,由于篇幅原因,笔者只介绍了 js 层面的实现过程,具体执行到 C++ 的部分笔者会在后续一一详述,敬请期待!