实现 Accessor

Accessor 旨在通过提供一种 getset 和 watch 属性的机制来简化类的开发。

本指南提供了常见访问器使用模式的指南。请点击以下链接以获取有关如何实现从 Accessor 派生的类的更多信息。有关 Accessor 属性的更多信息,请参阅使用属性指南主题。

如果使用 TypeScript,您需要安装 GeoScene API for JavaScript 4.x 类型定义。您可以在 jsapi-resources Github 存储库中使用其命令行语法访问这些类型。

扩展 Accessor

API 中的许多类都可扩展 Accessor 类。这些类可以公开可能具有唯一特征的可观察属性,例如只读或计算。在后台,Accessor 使用 dojo/_base/declare 创建类。

创建一个简单子类

简单子类 - TS简单子类 - JS
        
1
2
3
4
5
6
7
8
import Accessor = require("geoscene/core/Accessor");

import { subclass } from "geoscene/core/accessorSupport/decorators";

@subclass("geoscene.guide.Color")
class Color extends Accessor {

}

使用 Accessor 的 Mixins

GeoScene API for JavaScript 使用 mixins 来构建其类。请阅读此优秀文章,深入探讨使用 TypeScript 的 mixins。

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

Defining an Accessor mixin - TSDefining an Accessor mixin - 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
37
38
import Accessor = require("geoscene/core/Accessor");
import { subclass } from "geoscene/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> indicates 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("geoscene.guide.Evented")
  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 {
      // ...
    }
  }

  return Evented;
}

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

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

Using an Accessor mixin - TSUsing an Accessor mixin - JS
          
1
2
3
4
5
6
7
8
9
10
import Accessor = require("geoscene/core/Accessor");
import { subclass } from "geoscene/core/accessorSupport/decorators";

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

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

属性

定义一个简单属性

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

Simple property - TSSimple property - JS
                    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Accessor = require("geoscene/core/Accessor");

import { subclass, property } from "geoscene/core/accessorSupport/decorators";

@subclass("geoscene.guide.Color")
class Color extends Accessor {

  @property()
  r: number = 255;

  @property()
  g: number = 255;

  @property()
  b: number = 255;

  @property()
  a: number = 1;

}

定义自定义 getter 和 setter

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

Setter property - TSSetter property - 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
37
import Accessor = require("geoscene/core/Accessor");

import { subclass, property } from "geoscene/core/accessorSupport/decorators";

@subclass("geoscene.guide.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;
    }
  }

}

定义只读属性

以下语法显示如何设置只读属性。

Read-only - TSRead-only - JS
                       
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Accessor = require("geoscene/core/Accessor");

import { subclass, property } from "geoscene/core/accessorSupport/decorators";

@subclass("geoscene.guide.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
    });
  }
}

定义代理属性

有时,除了可能对值执行转换之外,您还需要在读取和写入时代理属性。例如,公开内部成员属性。

Proxy property - TSProxy property - JS
                       
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Accessor = require("geoscene/core/Accessor");

import { subclass, aliasOf } from "geoscene/core/accessorSupport/decorators";

@subclass("geoscene.guide.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;

}

计算属性

定义计算属性

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

Computed property - TSComputed property - JS
                    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Accessor = require("geoscene/core/Accessor");
import { subclass, property } from "geoscene/core/accessorSupport/decorators";

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

  @property()
  lastName: string;

  @property({
    readOnly: true,
    // define the property dependencies
    dependsOn: ["firstName", "lastName"]
  })
  get fullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }
}

定义可写的计算属性

Writable computed property - TSWritable computed property - 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 = require("geoscene/core/Accessor");
import { subclass, property } from "geoscene/core/accessorSupport/decorators";

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

  @property()
  lastName: string;

  @property({
    // define the property dependencies
    dependsOn: ["firstName", "lastName"]
  })
  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;
    }

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

通知属性更改

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

Writable computed property - TSWritable computed property - 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
import Accessor = require("geoscene/core/Accessor");

import { subclass, property } from "geoscene/core/accessorSupport/decorators";

@subclass("geoscene.guide.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");
  }

}

自动转换

定义属性类型

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

Define the property type - TSDefine the property type - JS
                   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Graphic = require("geoscene/Graphic");

import Accessor = require("geoscene/core/Accessor");
import Collection = require("geoscene/core/Collection");

import { subclass, property } from "geoscene/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 = "streets-vector"

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

Define a casting method - TSDefine a casting method - 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 = require("geoscene/core/Accessor");
 import { subclass, property, cast } from "geoscene/core/tsSupport/declare";

 @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 that 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 that clamp the value that
     // will be set on a between 0 and 1
     return Math.max(0, Math.min(1, value));
   }
 }

附加信息

请参阅这些附加链接以获取更多信息:

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.

Navigated to 实现 Accessor