我有一个名为 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中提取的,所以我非常希望不要对其进行任何更改,因为它会使更新更加尴尬/容易失败。
CircleMarker
类不是 interface
,所以我看不出它的相关性。挑战在于接口只是 TypeScript 中的编译时检查,因此可以“扩展”它们。然而,类是一个实际的 JavaScript 结构,并且要干净和一致地扩展要复杂得多。
如果您不控制原始定义文件,并且无法对其进行调整,那么很遗憾,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
关键字来做到这一点。您可以在此处对功能请求进行投票: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;
}
}
这是我尝试过的,对我来说感觉比较舒服
declare module L {
export class CircleMarkerEx {
constructor(source: CircleMarker);
public bindLabel(name: string, options: any): CircleMarker;
}
export function ex(cm: CircleMarker): CircleMarkerEx;
}
其中 CircleMarkerEx
和 ex
可以定义为
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", {});
但还是有点奇怪
这可能吗?
declare module L {
export class MyCircleMarker extends CircleMarker{
bindLabel(name: string, options: any): CircleMarker;
}
}
然后将您的 CircleMarker 实例定义为 MyCircleMarker
编辑:这个答案是题外话。它谈论的是接口,而不是像问的那样的类
扩展 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 的工作方式
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!';
对于那些正在寻找新的/当前记录的解决方案的人,可以在以下位置找到:
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)
}
class
,DefinitelyTypes 库的类型不正确,最好通过扩充定义来修复它。