打字稿:向现有抽象添加额外的重载 class

Typescript: Adding additional overloads to an existing abstract class

我正在为包 Leaflet.Editable 开发 DefinitelyTyped 的一组定义。 Leaflet 值得注意的是,它使用部分自定义的 class 实现来允许扩展纯 JavaScript、as seen here 中的现有类型。只需在代码中的任何位置调用 import 'Leaflet.Editable' 即可为现有 Leaflet class 添加新功能,例如启用和禁用某些图层的编辑功能。

它没有在 TypeScript 中实现,因此在 @types/leaflet 中实现了这种类型化,所以我正在开发的包(@types/leaflet-editable)必须导入 leaflet 命名空间并扩展现有类型。

我必须扩展现有类型而不是添加新类型,因为还有其他库(例如 react-leaflet)使用这些类型。

例如,通过扩展 MapOptions 类型(通过 merging the interfaces),我能够向 react-leafletMapComponent 组件添加新属性,因为它道具扩展 Leaflet.MapOptions。如果我要创建一个新类型 EditableMapOptions,它不会被 react-leaflet 扩展,因此我将无法将这些属性添加到 MapComponent.

我能够扩展几个现有接口,并添加新接口,例如 VertexEventHandlerFn(表示提供 VertexEvent 的事件回调)。

问题是 @types/leaflet 如何实现 map.on('click', (event: LeafletEvent) => {}) 功能。这是来自 the DefinitelyTyped repo 的片段:

export abstract class Evented extends Class {
    /**
     * Adds a listener function (fn) to a particular event type of the object.
     * You can optionally specify the context of the listener (object the this
     * keyword will point to). You can also pass several space-separated types
     * (e.g. 'click dblclick').
     */
    // tslint:disable:unified-signatures
    on(type: string, fn: LeafletEventHandlerFn, context?: any): this;
    on(type: 'baselayerchange' | 'overlayadd' | 'overlayremove',
        fn: LayersControlEventHandlerFn, context?: any): this;
    on(type: 'layeradd' | 'layerremove',
        fn: LayerEventHandlerFn, context?: any): this;
    ...

如您所见,它使用了一个抽象 class 实现了一个通用定义,当用户包含多个由空格分隔的事件时,以及几个特定事件,当提供特定参数时,这些事件包含适当的为他们的事件处理程序打字。

我想向其中添加我的函数,但我找不到任何方法向现有抽象添加额外的重载 class。这里要注意的是,所有这些 on 的重载都是由 Leaflet 中的同一行代码实现的,因此向抽象 class 添加新方法所面临的问题(稍后将由关联的 JavaScript) 不存在于此处。

我尝试简单地尝试重新声明 abstract class Evented 以向其添加更多方法,但 TypeScript 告诉我它已经定义。我的研究只发现 TypeScript 文档指示我应该使用 mixins,我不能这样做,因为我需要修改现有的 class,创建一个新的也不能解决问题。

Here is my current TypeScript implementation of leaflet-editable so far.

documentation for module augmentation看可能不是很清楚,但是如果你想合并到实例类型的命名class,你可以这样做因此,通过添加与 class 同名的 interface。当你写 class Foo {} 时,它把名为 Foo 的类型当作一个接口,你可以合并到它里面。例如:

import { Draggable, LeafletEvent } from 'leaflet';

interface PeanutButterEvent extends LeafletEvent {
  chunky: boolean
}

declare module 'leaflet' {
  interface Evented {
    on(type: "peanut-butter", fn: (x: PeanutButterEvent) => void): this;
  }
}

const draggable = new Draggable(new HTMLElement()) // whatever
draggable.on("peanut-butter", e => console.log(e.chunky ? "Chunky" : "Creamy")); // ok
draggable.on("resize", e => console.log(e.oldSize)); // still works

在这里,我将 on() 的另一个重载添加到名为 Eventedinterface


另一方面,如果你想合并到 classstatic 类型,你可以通过添加到 namespaceclass 同名。当你写 class Foo {} 时,它会将名为 Foo 的值视为一个命名空间,你可以合并到其中。例如:

import { Draggable, Evented } from 'leaflet';

declare module 'leaflet' {
  namespace Evented {
    export function hello(): void;
  }
}
Evented.hello = () => console.log("hello"); // implement if you want
Draggable.hello(); // okay
Draggable.mergeOptions({}); // still works

在这里,我将另一个名为 hello() 的静态方法添加到名为 Eventednamespace

Playground link to code