自定义微件基础知识

简介

微件是可复用的用户界面组件,是提供丰富用户体验的关键。GeoScene Maps SDK for JavaScript 提供了将完全自定义微件作为 HTML 元素包含在视图中的能力。这涉及使用全新功能开发您自己的视图,并且其与扩展微件的 ViewModel 是分开的。

构建自定义微件时,建议将 @geoscene/core ES 模块与您选择的前端 JavaScript 框架一起使用。React、Angular、Vue.js 等框架不是必需的,但它们提供了最大的可扩展性。您可以创建多个与 SDK 松散耦合的微件,这些微件遵循框架众所周知的 UI/UX 模式和组件生命周期。

开发要求

在创建自己的自定义微件之前,您需要确保具备所需的最低要求。这些将根据您的微件要求而有所不同。下面列出的是微件开发的最低要求。

TypeScript

建议的微件开发方法是使用 TypeScript。此方法的优势之一是可使用装饰器来增强功能。TypeScript 设置指南页面提供了一些使用 GeoScene Maps SDK for JavaScript 设置 TypeScript 开发环境的基本步骤。还有大量在线资源,详细介绍了 TypeScript 是什么,为什么使用它,以及如何使用它。熟悉这些基础知识将使微件开发过程变得更加容易。

JSX

JSX 是一种 JavaScript 扩展语法,其允许以类似于 HTML 的方式描述微件 UI。它通常与 React 相关联,但也可在其他实现中使用。它看起来类似于 HTML,因为它可以与 JavaScript 内联使用。自定义微件是使用 .tsx 扩展构建的,该扩展将 TypeScript 与 JSX 结合在一起,并直接编译为 JavaScript。

熟悉 geoscene/core/Accessor

Accessor 是 4.x 的核心功能之一,也是所有 SDK 类的基础。这包括自定义微件,因为它们扩展了现有 API 类以创建子类。自定义微件通过调用 super() 方法从其父类继承方法和属性。有关其他详情及使用模式,请参阅实现 Accessor 主题。

@geoscene/core

@geoscene/core ES 模块具有与 AMD 模块相同的底层 API 功能。建议使用 @geoscene/core 来构建自定义微件,因为 ES 模块与几乎所有主要的 JavaScript 框架和构建工具都能无缝工作,因为它们不需要专门的 AMD 加载程序,并且可以在浏览器和现代构建工具中本地使用。这些模块使用 npm 本地安装在开发计算机上。

微件生命周期

在开始开发之前,对微件生命周期有一个大致的了解非常重要。无论微件的类型如何,特定于其生命周期的一般概念是相同的。它们是:

  1. constructor (params) - 在设置任何所需属性时,这是最初创建微件的地方。由于微件派生自 Accessor,因此您可以访问获取、设置和监视属性,如使用属性主题中所述。
  2. postInitialize() - 此方法在创建微件之后,但在渲染 UI 之前调用。
  3. render() - 这是唯一的必需方法,用于渲染 UI。
  4. destroy() - 释放微件实例的方法

TypeScript 装饰器

微件开发可利用 TypeScript 装饰器,而 API 使用装饰器作为创建类的底层粘合剂。这允许我们在设计时增强现有属性、方法和构造函数中的常见行为。我们将在下面讨论最常见的微件装饰器类型。

@subclass()

@subclass() 装饰器用于扩展 API 类。它还允许您在构造函数中将 declaredClass 属性指定为字符串。这有助于 API 区分要扩展的现有类,其中 declaredClass只读的,且是您正在构建的自定义类。

以下代码段导入并扩展了基本 Widget类,并在 render 方法中定义了 UI。JSX 用于定义 UI。在此简单方案中,将创建一个以 John Smith 为其内容的 div 元素。

代码块使用深色复制
          
1
2
3
4
5
6
7
8
9
10
import Widget from "@geoscene/core/widgets/Widget.js";

@subclass("geoscene.widgets.HelloWorld")
class HelloWorld extends Widget {
  render() {
    return (
      <div>John Smith</div>
    );
  }
}

@property()

@property() 装饰器用于定义 Accessor 属性。使用此装饰器定义的任何属性现在都可进行 getset。此外,您还可以 watch 任何属性更改。

代码块使用深色复制
  
1
2
@property()
name: string;

@aliasOf()

@aliasOf() 装饰器用于在它所装饰的属性与其成员之一的内部属性之间创建双向绑定。

代码块使用深色复制
  
1
2
@aliasOf("initialCenter.extent")
extent: Extent;

微件实现

以下步骤提供了实现您自定义微件时所需步骤的高度概述:

创建新的微件类

首先扩展 Widget 基类并在 @subclass() 构造函数中指定 declaredClass

代码块使用深色复制
      
1
2
3
4
5
6
import Widget from "@geoscene/core/widgets/Widget.js";

@subclass("geoscene.widgets.HelloWorld")
class HelloWorld extends Widget {

}

实现属性和方法

接下来,您可以实现特定于该微件的任何属性和/或方法。此代码片段演示了如何利用这些属性的装饰器

代码块使用深色复制
      
1
2
3
4
5
6
// Create 'name' property
@property()
name: string = "John Smith";

// Create private _onNameUpdate method
private _onNameUpdate(): string { return '${this.name}';}

默认情况下,元素中引用的函数会将 this 设置为实际元素。您可以选择性地使用 bind 属性来更新 this。下面绑定了 _onNameUpdate 回调方法,以便在监听 name 属性更新时使用。这显示在以下 postInitialize 方法中。

代码块使用深色复制
        
1
2
3
4
5
6
7
8
class HelloWorld extends Widget {

  constructor(params?: any) {
    super(params);
    this._onNameUpdate = this._onNameUpdate.bind(this);
  }

}

当微件属性准备就绪时,可在渲染之前调用 postInitialize 方法。在以下代码片段中,我们正在监视 name 属性。更新后,它将调用 _onNameUpdate 回调方法。watchUtils.init() 调用将返回一个 WatchHandle 对象,然后将其传递到 own() 中。这有助于在微件被破坏后清理所有资源。

代码块使用深色复制
        
1
2
3
4
5
6
7
8
  import * as watchUtils from "@geoscene/core/core/watchUtils.js";

  postInitialize() {
    const handle = watchUtils.init(this, "name", this._onNameUpdate);

    // Helper used for cleaning up resources once the widget is destroyed
    this.own(handle);
  }

渲染微件

实现属性后,使用 JSX 渲染微件的 UI。这在微件的 render 方法中处理,这是微件实现所需的唯一必需方法。

请注意,尚不支持作为 JSX 元素创建的微件。例如,以下代码段将不起作用。

const search = <Search view={view} />;

代码块使用深色复制
       
1
2
3
4
5
6
7
render() {
  return (
    <div>
      {this._onNameUpdate()}
    </div>
  );
}

最后,在微件上调用 destroy 将处理微件并释放使用下面的 postInitialize 中引用的 own() 方法注册的所有资源。

代码块使用深色复制
      
1
2
3
4
5
6
postInitialize() {
  const handle = watchUtils.init(this, "name", this._onNameUpdate);

  // Helper used for cleaning up resources once the widget is destroyed
  this.own(handle);
}

导出模块

在代码页的最末尾,添加一行以导出模块。导出使微件功能通过 import 语句可用。

代码块使用深色复制
 
1
export default HelloWorld;

微件渲染

下面列出的属性可用于渲染微件:

  • classes:此实用方法用于构建微件的类属性的值。这有助于简化 CSS 类设置。
  • styles:允许动态更改样式。
  • afterCreate:此回调方法在将节点添加到 DOM 后执行。任何子节点和属性都已应用。在渲染器中使用此方法访问真实 DOM 节点。也可使用每个元素。
  • afterUpdate:每次更新节点时,都会执行此回调方法。
  • bind:此属性用于为事件处理程序设置 this 值。
  • key:这用于唯一标识在其同级中的 DOM 节点。如果同级元素具有相同选择器,并且这些元素是动态添加/删除的,这一点很重要。
classesclassesstylesafterCreateafterUpdatebindkey
代码块使用深色复制
           
1
2
3
4
5
6
7
8
9
10
11
// Newer method which works with the Widget's classes helper method.
render() {
  const dynamicClass = {
    [CSS.bold]: this.isBold,
    [CSS.italic]: this.isItalic
  }

  return {
    <div class={this.classes(CSS.base, dynamicClass)}>Hello World!</div>
  };
}

除了上面提到的方法,还有一个便利的 storeNode 方法。您可以使用它来分配引用变量的 HTMLElement DOM 节点。这将使用自定义数据属性 data-node-ref 来存储对元素 DOM 节点的引用。为使其正常工作,还必须将其绑定到微件实例,例如 bind={this},如以下代码片段所示。

代码块使用深色复制
           
1
2
3
4
5
6
7
8
9
10
11
// Assign the data-node-ref attribute to a DOM node value.
// It should be used in conjunction with the `bind` property
// and is used when working with the storeNode convenience method.

rootNode: HTMLElement = null;

render() {
  return (
    <div afterCreate={storeNode} bind={this} data-node-ref="rootNode" />
  );
}

其他信息

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

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