【问题标题】:How do I extend a TypeScript class definition in a separate definition file?如何在单独的定义文件中扩展 TypeScript 类定义?
【发布时间】:2014-06-06 16:40:11
【问题描述】:

我有一个名为 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

标签: typescript


【解决方案1】:

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

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)
}

【讨论】:

    【解决方案2】:

    编辑:这个答案离题了。它讨论的是接口,而不是要求的类

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

    google.maps.Marker type definition 为例:

    // 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:它可以正常工作。

    DefinitelyTyped 就是这样工作的

    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
    【解决方案3】:

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

    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", {});
    

    不过还是有点奇怪

    【讨论】:

      【解决方案4】:

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

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

      您应该能够使用带有类型断言的interface,而不是使用any

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

      然后:

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

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

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

      【讨论】:

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

      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 已过时。
      【解决方案6】:

      这可能吗?

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

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

      【讨论】:

      • 感谢您的回复。这是一种选择,但它要求开发人员意识到存在这种特殊要求。一个“正常工作”的解决方案会更好,例如实际扩展类定义。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-11-13
      • 1970-01-01
      • 2023-03-17
      • 1970-01-01
      • 2018-07-23
      • 1970-01-01
      • 2022-11-11
      相关资源
      最近更新 更多