ChatGPT解决这个技术问题 Extra ChatGPT

如何在单独的定义文件中扩展 TypeScript 类定义?

我有一个名为 Leaflet 的 JS 库,它有一个现有的 TypeScript 定义文件。

我希望使用一个插件来扩展传单中的一些对象,并具有额外的功能。

在现有的 TypeScript 定义文件中,对象被定义为类而不是接口。

例如

declare module L {
    function circleMarker(latlng: LatLng, options?: PathOptions): CircleMarker;

    export class CircleMarker extends Circle {
        constructor(latlng: LatLng, options?: PathOptions);
        setLatLng(latlng: LatLng): CircleMarker;
        setRadius(radius: number): CircleMarker;
        toGeoJSON(): any;
    }
}

如果我尝试在单独的文件中再次定义它,则会收到有关“重复标识符'CircleMarker'。”的错误。

declare module L {
    export class CircleMarker {
        bindLabel(name: string, options: any): CircleMarker;
    }
}

这是有道理的,因为它是一个类而不是一个接口,但是在这种情况下,有没有办法在不更改原始定义文件的情况下扩展这个类定义?

基本定义文件是通过nuget从DefiniteTyped中提取的,所以我非常希望不要对其进行任何更改,因为它会使更新更加尴尬/容易失败。

现在我只是绕过了烦人但可行的类型检查。 (marker).bindLabel("Hello World", { });
您基本上是在尝试使“部分”工作(例如在 C# 中),这不是 TypeScript 的功能。这在脚本语言中并不简单,因为所有内容都没有像在 .NET 程序集中那样很好地打包。
WiredPrairie:C# 等价物将是部分接口,因为这是关于类型定义而不是实际实现。 Typescript 已经支持接口 blogs.msdn.com/b/typescript/archive/2013/01/24/… “TypeScript 中的接口是开放的,这意味着您可以通过简单地编写另一个接口块来将自己的成员添加到接口中。”但它目前不适用于课程。我怀疑这是出于可用性/设计原因,而不是 .Net 元数据更好。
我了解 C# 部分——以及缺少实际的 TypeScript 部分。但是,CircleMarker 类不是 interface,所以我看不出它的相关性。挑战在于接口只是 TypeScript 中的编译时检查,因此可以“扩展”它们。然而,类是一个实际的 JavaScript 结构,并且要干净和一致地扩展要复杂得多。
这是相关的,因为我正在为现有的 JS 代码创建定义,而不是实现我自己的。理想情况下,制作原始定义文件的人应该使用接口,但他们没有,我现在被困在试图解决他们的设计选择。有各种关于使类像接口一样开放的票,typescript.codeplex.com/workitem/917

W
WiredPrairie

如果您不控制原始定义文件,并且无法对其进行调整,那么很遗憾,TypeScript 目前不支持您尝试执行的操作。 TypeScript 中的 interface 是唯一允许合理扩展的构造,因为它只是编译时/语法检查,而不是运行时操作。

您不能仅使用 TypeScript 为 TypeScript 中的 class 扩展新功能(并期望代码完成/智能感知按预期工作)。您当然可以将函数添加到 CircleMarker 类的 prototype,但它们对 Intellisense 不可用,并且除非您使用类型断言,否则将无法编译。

除了使用 any,您应该能够使用带有类型断言的 interface

declare module L {
    export interface CircleMarkerEx {
        bindLabel(name: string, options: any): CircleMarker;
    }
}

然后:

var cm = <L.CircleMakerEx> circle.bindLabel("name", {});

值得庆幸的是,它不会增加任何运行时开销,只是增加了一些额外的输入(双关语!)。

CodePlex 上曾提出过 "mix-ins" 之类的建议,但尚未实施。即使是混合建议也不会完全直接使用,并且对于不是完全用 TypeScript 编写的库也不能很好地工作(因为很容易拥有无法安全构建的 JavaScript 代码,例如混入)。


一年后这个答案有更新吗?扩展以前定义的项目的原型是我真的很难做的事情。
不,这是相同的行为。
现在?我听说可以从模块声明中合并类型,但仍然没有找到解决类似问题的方法
我需要这个。对于 class,DefinitelyTypes 库的类型不正确,最好通过扩充定义来修复它。
对于@trusktr 和所有对DefiniteTyped 有问题的人,如果您的项目使用npm 或yarn,patch-package 会派上用场。
b
basarat

您不能使用 class 关键字来做到这一点。您可以在此处对功能请求进行投票:https://typescript.codeplex.com/workitem/917

但是,您可以使用 interfaces 模拟类,如问题的解决方法 (https://typescript.codeplex.com/workitem/917) 中所示。在你的情况下

declare module L {
    function circleMarker(latlng: LatLng, options?: PathOptions): CircleMarker;

    declare var CircleMarker: CircleMarkerStatic;
    export interface CircleMarkerStatic{
      new (latlng: LatLng, options?: PathOptions): CircleMarker;
    }

    export interface CircleMarker {
        setLatLng(latlng: LatLng): CircleMarker;
        setRadius(radius: number): CircleMarker;
        toGeoJSON(): any;
    }
}

并扩展它

declare module L {
    export interface CircleMarker {
        bindLabel(name: string, options: any): CircleMarker;
    }
}

不幸的是,这将需要更改不受我直接控制的原始定义。对于那些可以轻松更改原始定义的人来说,这是一个不错的选择。谢谢!
仅供参考,功能请求 URL 已过时。
R
Roy Yin

这是我尝试过的,对我来说感觉比较舒服

declare module L {
    export class CircleMarkerEx {
        constructor(source: CircleMarker);
        public bindLabel(name: string, options: any): CircleMarker;
    }
    export function ex(cm: CircleMarker): CircleMarkerEx;
}

其中 CircleMarkerExex 可以定义为

class CircleMarkerExtender extends CircleMarker {    
    public b() {
        return `${this.a()} extended`;
    }
}
CircleMarker.prototype = Object.create(CircleMarkerExtender.prototype);
CircleMarker.prototype.constructor = CircleMarker;

export function ex(cm: CircleMarker) {
    return cm as CircleMarkerExtender;
}

然后

let cm = ex(circle).bindLabel("name", {});

但还是有点奇怪


R
Rune Jeppesen

这可能吗?

declare module L {
    export class MyCircleMarker extends CircleMarker{
        bindLabel(name: string, options: any): CircleMarker;
    }
}

然后将您的 CircleMarker 实例定义为 MyCircleMarker


谢谢回复。这是一种选择,但它要求开发人员意识到存在这种特殊要求。 “正常工作”的解决方案会更可取,例如实际扩展类定义。
t
tanguy_k

编辑:这个答案是题外话。它谈论的是接口,而不是像问的那样的类

扩展 TypeScript (v3.6) 外部类型定义没有任何问题。

google.maps.Marker 类型定义的示例:

// File types/googlemaps-marker-hello.d.ts or
// whatever file name you like, it doesn't matter
declare namespace google.maps {
  interface Marker {
    hello: string;
  }
}

// Later on inside src/MyCode.ts
const marker = new google.maps.Marker();
marker.hello = 'Hello, World!';

您不必修改您的 tsconfig.json:它可以正常工作。

这是 DefinitiveTyped 的工作方式

请参阅https://github.com/DefinitelyTyped/DefinitelyTyped/blob/edb4917e1bbd0d860b51e7806775cb505f858e36/types/webpack-dev-server/index.d.ts#L206-L211

declare module 'webpack' {
  interface Configuration {
    devServer?: WebpackDevServer.Configuration;
  }
}

webpack-dev-server 类型定义扩展了 webpack 类型定义。如果您使用 @types/webpack 而不是 @types/webpack-dev-server 您将没有 devServer 属性。

另一个例子:

declare namespace NodeJS {
  interface Global {
    hello: string;
  }
}

global.hello = 'Hello, World!';

使用接口很容易。但我试图通过课程来做到这一点。 “在现有的 TypeScript 定义文件中,对象被定义为类而不是接口。”据我所知,这仍然是不可能的。这里有一个沙盒供您尝试:codesandbox.io/s/typescript-playground-n35xm
J
Jack Punt

对于那些正在寻找新的/当前记录的解决方案的人,可以在以下位置找到:

https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation

我用它来扩展 google-protobuf 生成的类的原型以添加额外的谓词。

import { CgMessage, CgType } from "./CgProto";
declare module './CgProto' {
  interface CgMessage {
    expectsAck(): boolean 
  }
}
CgMessage.prototype.expectsAck = function() {
  return [CgType.send, CgType.join, CgType.leave].includes(this.type)
}