# 18.2:页面刷新机制浅析

笔者在第二章第 1 节介绍的《简单计数器示例》里说过 @State 修饰符的功能,当它修饰的属性值变化时会触发 build() 方法的调用从而实现页面刷新的目的,本节笔者简单介绍一下 @State 修饰符是如何做到页面刷新的。

# 18.2.1:ObservedPropertySimple初识

根据上节对编译后的 abcjs 文件分析,我们发现项目打包后 @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);
  }

  // 省略部分方法
}
1
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 表示属性的名称,在构造方法内先调用了父类的构造方法,把 owningViewpropertyName 传递过去,然后调用 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!`);
      }
    });
  }

  // 省略部分代码
}
1
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() 方法内部循环遍历保存的 Viewid 值,然后从 SubscriberManager 里边获取对应的 View,当获取到后调用它的 propertyHasChanged() 方法并把属性名字传递过去,那么 View 是在什么时候添加进 SubscriberManager 里边的呢?根据反编译的 abc 文件可知,index 被编译成了继承自 ViewView 的源码在 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);
    }
  }

}
1
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 的源码可知,在它的构造方法内调用了 SubscriberManageradd() 方法并把自己添加进了 SubscriberManager 的内部集合里,因此在 ObservedPropertyAbstractnotifyHasChanged() 方法内会执行到 ViewpropertyHasChanged() 方法,而该方法内部又调用到了父类 NativeViewsyncInstanceId() 方法和 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;
}
1
2
3
4
5
6
7
8

NativeView 定义的方法具体实现是在底层用 C++ 实现的,由于篇幅原因笔者会在后续做详细分析,到目前为止已经清楚了页面的刷新流程,也就是说在 js 层面 @State 修饰的属性值变化了,先执行 ObservedPropertyAbstractnotifyHasChanged() 方法,该方法内部找到属性变化所在的 View,调用该 ViewmarkNeedUpdate() 方法通知 C++ 层面,该 View 的属性值有变化,C++ 层在接收到信令后做后续的一系列页面刷新逻辑……

# 18.2.2:ObservedPropertySimple类结构

ArkUI 开发框架提供的状态管理使用起来的非常方便,代码量也非常精简,对开发者精简也就意味着 ArkUI 开发框架需要在内部实现相当多的复杂能力,由于篇幅原因,笔者就不在介绍有关状态管理的其它类型实现了,笔者简单梳理了下 state_mgmt 目录下的状态管理类结构图,读者可根据该结构图自己做下其它类型的实现分析。

18_2_2_1

# 18.2.3:小结

本节笔者简单介绍了 @State 修饰符引发页面刷新的流程,由于篇幅原因,笔者只介绍了 js 层面的实现过程,具体执行到 C++ 的部分笔者会在后续一一详述,敬请期待!

(adsbygoogle = window.adsbygoogle || []).push({});
请作者喝杯咖啡

津公网安备 12011402001367号

津ICP备2020008934号-2