主题
实现 Accessor
字数统计: 2k 字
阅读时长: 约 5 分钟
当前版本: 4.29
本指南提供了常见 Accessor 使用模式的一些示例。Accessor 旨在通过提供一种 get、set 和 watch 属性的机制来简化自定义 API 类的开发。
以下代码片段包括用于本地构建的 @geoscene/core ES 模块 (ESM),以及 vanilla JavaScript 中的 AMD 示例。
如果使用 AMD 模块和 TypeScript,则需安装 GeoScene Maps SDK for JavaScript4.x 类型定义。如果您使用的是 @geoscene/core ES 模块,则键入将包含在安装中。有关更多信息,请参阅设置 Typescript 环境。
扩展 Accessor
API 中的许多类都可扩展 Accessor 类。这些类可以公开可能具有唯一特征的可观察属性,例如只读或计算。
创建简单子类
注: 使用 JavaScript 和 TypeScript 创建 API 子类之间存在重要差异。
当通过 JavaScript 构建应用程序时,则使用 createSubclass()
方法,该方法会自动调用 super()
。当通过 TypeScript 构建应用程序时,则使用 @subclass() 装饰器和 extends
关键字。
此外,当使用 TypeScript 装饰器 (如 @subclass()
) 时 ,为了向后兼容性,需要将 useDefineForClassFields
标志设置为 false
。有关更多信息,请参阅 TSConfig 参考。 declaredClass
属性在构造函数中指定为字符串,它有助于 API 区分正在扩展的现有类和正在构建的自定义类。在 API 中,declaredClass
是只读的。此属性在 JavaScript 和 TypeScript 中提供相同的功能。但实现却是不同的,如下面的示例所示。
js
import Accessor from "@geoscene/core/core/Accessor.js";
import { subclass } from "@geoscene/core/core/accessorSupport/decorators.js";
// declaredClass is "custom.Color"
@subclass("custom.Color")
class Color extends Accessor {
// ...
}
使用 Accessor 的 Mixins
GeoScene Maps SDK for JavaScript使用 mixins 来构建其类。mixin 是一个函数,用于在构建子类时帮助创建超类。请阅读此优秀文章,深入了解使用 TypeScript 的 mixins。
注:建议在构建 mixins 时使用 TypeScript,以利用静态类型和类型安全。
首先,定义 EventedMixin
以将事件系统添加到类中。
定义 Accessor mixin - ESM TS定义 Accessor mixin - ESM TS定义 Accessor mixin - AMD JS
js
import Accessor from "@geoscene/core/core/Accessor.js";
import { subclass } from "@geoscene/core/core/accessorSupport/decorators.js";
// 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")
return 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
类扩展了最终的子类。
js
import Accessor from "@geoscene/core/core/Accessor.js";
import { subclass } from "@geoscene/core/core/accessorSupport/decorators";
// import the newly created custom mixin
import { EventedMixin } from "custom/EventedMixin.ts";
@subclass("custom.Collection")
export class Collection extends EventedMixin(Accessor) {
// Collection extends a super class composed of Accessor and EventedMixin.
}
属性
定义简单属性
如果您希望创建一个简单的、可观察且不需要任何其他行为的属性,请应使用以下语法。您可以为基本属性值定义默认值和类型。如果使用 TypeScript,则可以在构造函数中设置默认属性值。
js
import Accessor from "@geoscene/core/core/Accessor.js";
import { subclass, property } from "@geoscene/core/core/accessorSupport/decorators.js";
@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。以下片段显示了此情况。
js
import Accessor from "@geoscene/core/core/Accessor.js";
import { subclass, property } from "@geoscene/core/core/accessorSupport/decorators.js";
@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。
js
import Accessor from "@geoscene/core/core/Accessor.js";
import { subclass, property } from "@geoscene/core/core/accessorSupport/decorators.js";
@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,
lastName
});
}
}
定义代理属性
以下代码片段演示了如何在内部属性上创建双向绑定。
js
import Accessor from "@geoscene/core/core/Accessor.js";
import Collection from "@geoscene/core/core/Collection.js";
import { subclass, property, aliasOf } from "@geoscene/core/core/accessorSupport/decorators.js";
@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;
}
计算属性
定义计算属性
当属性值依赖于许多其他属性时,可能需要使用此方法。这些属性始终是只读的。
js
import Accessor from "@geoscene/core/core/Accessor.js";
import { subclass, property } from "@geoscene/core/core/accessorSupport/decorators.js";
@subclass()
class Person extends Accessor {
@property()
firstName: string;
@property()
lastName: string;
@property({
readOnly: true
})
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
定义可写的计算属性
js
import Accessor from "@geoscene/core/core/Accessor.js";
import { subclass, property } from "@geoscene/core/core/accessorSupport/decorators.js";
@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;
}
const [firstName, lastName] = value.split(" ");
this.set("firstName", firstName);
this.set("lastName", lastName);
this.set("fullName", value);
}
}
通知属性更改
有时属性发生更改时,无法通知。Accessor 具有一个内部方法来通知任何更改,这会将属性标记为 dirty。下次访问该属性时,将重新评估其值。
js
import Accessor from "@geoscene/core/core/Accessor.js";
import { subclass, property } from "@geoscene/core/core/accessorSupport/decorators.js";
@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");
}
}
自动转换
定义属性类型
可以为类属性定义类型。
js
import Graphic from "@geoscene/core/Graphic.js";
import Accessor from "@geoscene/core/core/Accessor.js";
import Collection from "@geoscene/core/core/Collection.js";
import { subclass, property } from "@geoscene/core/core/accessorSupport/decorators.js";
@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
。
js
import Accessor from "@geoscene/core/core/Accessor.js";
import { subclass, property, cast } from "@geoscene/core/core/accessorSupport/decorators.js";
@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));
}
}
其他信息
有关详细信息,请参阅以下附加链接: