实现 Accessor

本指南提供了常见 Accessor 使用模式的示例。Accessor 旨在通过提供一种 getsetwatch 属性的机制来简化类的开发。

下面的代码片段包括用于本地构建的 @geoscene/core ES 模块(ESM),以及原版 JavaScript 中的 AMD 示例。

如果使用 AMD 模块TypeScript,则需要安装 GeoScene Maps SDK for JavaScript 4.x 类型定义。如果您使用的是 @geoscene/core ES 模块,则键入将包含在安装中。有关更多信息,请参阅设置 Typescript 环境

扩展 Accessor

API 中的许多类都可扩展 Accessor 类。这些类可以公开可能具有唯一特征的可观察属性,例如只读或计算。

创建一个简单的子类

当使用 createSubclass() 使用 JavaScript 构建应用程序时,此方法会自动调用 super()。当使用 TypeScript 进行构建时,请使用 @subclass() 装饰器和 extends 关键字。

declaredClass 属性在构造函数中指定为字符串,它有助于 API 区分正在扩展的现有类和正在构建的自定义类。在 API 中,declaredClass只读的。此属性在 JavaScript 和 TypeScript 中提供相同的功能。

简单 ESM 子类 - TS简单 ESM 子类 - TS简单 AMD 子类 - JS
代码块使用深色复制
       
1
2
3
4
5
6
7
import Accessor from '@geoscene/core/core/Accessor';
import { subclass } from "@geoscene/core/core/accessorSupport/decorators";

@subclass("custom.Color")
class Color extends Accessor {
  // ...
} 

使用 GeoScene 在线资源 与 AMD 一起进行开发时,请参阅使用 AMD 模块构建指南主题以了解有关加载自定义模块的详细信息。

使用 Accessor 的 Mixins

GeoScene Maps SDK for JavaScript 使用 mixins 来构建其类。mixin 是一个函数,用于在构建子类时帮助创建超类。请阅读此优秀文章,深入探讨使用 TypeScript 的 mixins。

首先,定义 EventedMixin 以将事件系统添加到类中。

定义 Accessor mixin - ESM TS定义 Accessor mixin - ESM TS定义 Accessor mixin - AMD JS
代码块使用深色复制
                                
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
import Accessor from '@geoscene/core/core/Accessor';
import { subclass } from "@geoscene/core/core/accessorSupport/decorators";

// A type to represent a constructor function
type Constructor<T = object> = new (...args: any[]) => T;

// A type to represent a mixin function
// See for more details https://www.bryntum.com/blog/the-mixin-pattern-in-typescript-all-you-need-to-know/
type Mixin<T extends (...input: any[]) => any> = InstanceType<ReturnType<T>>;

// TBase extends Constructor<Accessor> indicating that `EventedMixin`
// expects the base class to extend `Accessor`, for example to be able to use the `watch` method.
export const EventedMixin = <TBase extends Constructor<Accessor>>(Base: TBase) => {

  @subclass("custom.EventedMixin")
  class Evented extends Base {

    // A first function defined by the mixin
    emit(type: string, event?: any): boolean {
      // ...
    }

    // Another function defined by the mixin
    on(type: string, listener: (event: any) => void): IHandle {
      // ...
    }
  }
}

// define the type of the mixin. This is useful to type properties that extends this mixin
// eg: `myProperty: EventedMixin;`
export type EventedMixinType = Mixin<typeof EventedMixin>; 

在此示例中,我们创建了一个超类,它扩展了 Accessor 并添加了 EventedMixin 的功能。然后,Collection 类可扩展最终的子类。

使用 Accessor mixin - ESM TS使用 Accessor mixin - ESM TS使用 AMD Accessor mixin - AMD JS
代码块使用深色复制
          
1
2
3
4
5
6
7
8
9
10
import Accessor from '@geoscene/core/core/Accessor';
import { subclass } from "@geoscene/core/core/accessorSupport/decorators";

// import the newly created custom mixin
import { EventedMixin } from "custom/EventedMixin";

@subclass("custom.Collection")
export class Collection extends EventedMixin(Accessor) {
  // Collection extends a super class composed of Accessor and EventedMixin.
} 

属性

定义一个简单属性

如果您希望拥有一个简单的、可观察且不需要任何其他行为的属性,则应使用以下语法。您可以为原始属性值定义默认值和类型。如果使用 TypeScript,则可以在构造函数中设置默认属性值。

简单属性 - ESM TS简单属性 - ESM TS简单属性 - AMD JS
代码块使用深色复制
                   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Accessor from '@geoscene/core/core/Accessor';
import { subclass } from "@geoscene/core/core/accessorSupport/decorators";

@subclass("custom.Color")
class Color extends Accessor {

  @property()
  r: number = 255;

  @property()
  g: number = 255;

  @property()
  b: number = 255;

  @property()
  a: number = 1;

} 

定义自定义 getter 和 setter

有时您可能需要确认、验证或转换属性上设置的值。在设置属性时,您可能还需要执行其他(同步)工作。this.set() 方法继承自 Accessor。以下片段显示了此情况。

Setter 属性 - ESM TSSetter 属性 - ESM TSSetter 属性 - AMD JS
代码块使用深色复制
                                    
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
import Accessor from '@geoscene/core/core/Accessor';
import { subclass } from "@geoscene/core/core/accessorSupport/decorators";

@subclass("custom.Collection")
class Collection extends Accessor {

  private _items: any[] = [];

  // Example: Define a custom property getter.
  //   Accessor caches the values returned by the getters.
  //   At this point `length` will never change.
  //   See the "Notify a property change" section
  @property()
  get length(): number {
    return this._items.length;
  }

  set length(value: number) {
    // Example: perform  validation
    if (value <= 0) {
      throw new Error(`value of length not valid: ${value}`);
    }

    // internally you can access the cached value of `length` using `_get`.
    const oldValue = this.get<number>("length");

    if (oldValue !== value) {
      // a setter has to update the value from the cache
      this.set("length", value);

      // Example: perform additional work when the length changes
      this._items.length = value;
    }
  }

} 

定义只读属性

以下语法演示如何设置只读属性。this.set() 方法继承自 Accessor。

只读 - ESM TS只读 - ESM TS只读 - AMD JS
代码块使用深色复制
                      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Accessor from '@geoscene/core/core/Accessor';
import { subclass } from "@geoscene/core/core/accessorSupport/decorators";

@subclass("custom.Person")
class Person extends Accessor {

  // Example: read-only property may not be externally set
  @property({ readOnly: true })
  firstName: string;

  @property({ readOnly: true })
  lastName: string;

  updateName(firstName: string, lastName: string): void {
    // We may still update the read-only property internally, which will change
    // the property and notify changes to watchers
    this.set({
      firstName: firstName,
      lastName: lastName
    });
  }
} 

定义代理属性

这些代码片段演示如何在内部属性上创建双向绑定。

代理属性 - ESM TS代理属性 - ESM TS代理属性 - AMD JS
代码块使用深色复制
                      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Accessor from '@geoscene/core/core/Accessor';
import { subclass } from "@geoscene/core/core/accessorSupport/decorators";

@subclass("custom.GroupLayer")
class GroupLayer extends Accessor {

  @property()
  sublayers: Collection = new Collection();

  // Define a property that reflects one in another object.
  @property({ aliasOf: "sublayers.length" })
  length: number;

  // Alternatively you can use the `@aliasOf` decorator
  //  @aliasOf
  //  length: number

  // You can also proxy a method from another object.
  @aliasOf("sublayers.add")
  add: (item: any) => void;

} 

计算属性

定义计算属性

当属性值依赖于许多其他属性时,您可能需要使用它。这些属性始终是只读的。

计算属性 - ESM TS计算属性 - ESM TS计算属性 - AMD JS
代码块使用深色复制
                  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Accessor from '@geoscene/core/core/Accessor';
import { subclass } from "@geoscene/core/core/accessorSupport/decorators";

@subclass()
class Person extends Accessor {
  @property()
  firstName: string;

  @property()
  lastName: string;

  @property({
    readOnly: true
  })
  get fullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }
} 

定义可写的计算属性

可写的计算属性 - ESM TS可写的计算属性 - ESM TS可写的计算属性 - AMD JS
代码块使用深色复制
                               
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
import Accessor from '@geoscene/core/core/Accessor';
import { subclass } from "@geoscene/core/core/accessorSupport/decorators";

@subclass()
class Person extends Accessor {
  @property()
  firstName: string;

  @property()
  lastName: string;

  @property()
  get fullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }

  set fullName(value: string) {
    if (!value) {
      this.set("firstName", null);
      this.set("lastName", null);
      this.set("fullName", null);

      return;
    }

    let [firstName, lastName] = value.split(" ");
    this.set("firstName", firstName);
    this.set("lastName", lastName);
    this.set("fullName", value);
  }
} 

通知属性更改

有时属性更改时无法通知。Accessor 具有一个内部方法来通知任何更改,这会将属性标记为脏。下次访问该属性时,将重新评估其值。

可写的计算属性 - ESM TS可写的计算属性 - ESM TS可写的计算属性 - AMD JS
代码块使用深色复制
                        
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Accessor from '@geoscene/core/core/Accessor';
import { subclass } from "@geoscene/core/core/accessorSupport/decorators";

@subclass("custom.Collection")
class Collection extends Accessor {

  private _items: any[] = [];

  @property({
    readOnly: true
  })
  get length(): number {
    return this._items.length;
  }

  add(item: any): void {
    this._items.push(item);

    // We know the value of `length` is changed.
    // Notify so that at next access, the getter will be invoked
    this.notifyChange("length");
  }

} 

自动转换

定义属性类型

可以为类的属性定义类型。

定义属性类型 - ESM TS定义属性类型 - ESM TS定义属性类型 - AMD JS
代码块使用深色复制
                 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Graphic from '@geoscene/core/Graphic';
import Accessor from '@geoscene/core/core/Accessor';
import Collection from '@geoscene/core/core/Collection';
import { subclass, property } from "@geoscene/core/core/accessorSupport/decorators";

@subclass()
class GraphicsLayer extends Accessor {

  @property({
    // Define the type of the collection of Graphics
    // When the property is set with an array,
    // the collection constructor will automatically be called
    type: Collection.ofType(Graphic)
  })
  graphics: Collection<Graphic>;

} 

定义转换属性的方法

有时,您需要在设置属性时确保其值类型。一个很好的例子是为特定值设置熟知预设名称,例如,map.basemap = "tianditu-vector"

如果尚未设置 type 元数据,则会自动为 Accessor 和原始类型创建适当的 cast

定义转换方法 - ESM TS定义转换方法 - ESM TS定义转换方法 - AMD JS
代码块使用深色复制
                                  
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
import Accessor from '@geoscene/core/core/Accessor';
import { subclass, property, cast } from "@geoscene/core/core/accessorSupport/decorators";

 @subclass()
 class Color extends Accessor {

   @property()
   r: number = 0;

   @property()
   g: number = 0;

   @property()
   b: number = 0;

   @property()
   a: number = 1;

   @cast("r")
   @cast("g")
   @cast("b")
   protected castComponent(value: number): number {
     // cast method to clamp the value that
     // will be set on r, g or b between 0 and 255
     return Math.max(0, Math.min(255, value));
   }

   @cast("a")
   protected castAlpha(value: number): number {
     // cast method to clamp the value that
     // will be set on a between 0 and 1
     return Math.max(0, Math.min(1, value));
   }
 } 

其他信息

有关详细信息,请参阅以下附加链接:

您的浏览器不再受支持。请升级您的浏览器以获得最佳体验。