ChatGPT解决这个技术问题 Extra ChatGPT

How do I extend a TypeScript class definition in a separate definition file?

I have a JS library called leaflet which has an existing TypeScript definition file.

I wish to use a plugin which extends some of the objects in leaflet with an extra function.

In the existing TypeScript definition file the objects are defined as classes rather than interfaces.

e.g.

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

If I try and define it a second time in a separate file then I get an error about "Duplicate Identifier 'CircleMarker'.".

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

This makes sense as it's a class and not an interface, but that being the case is there a way to extend this class definition without changing the original definition file?

The base definition file is pulled in from DefinitelyTyped via nuget so I have a very strong desire not to make any changes to it as it'll make updating much more awkward/prone to failure.

For now I'm simply bypassing the type checking which is annoying but workable. (marker).bindLabel("Hello World", { });
You're basically trying to make "partials" work (like in C# for example), which isn't a feature of TypeScript. It's not straightforward in a script language where everything isn't packaged up nicely like in an .NET assembly.
WiredPrairie: The C# equivalent would be partial interfaces as this is about the type definitions rather than actual implementations. Typescript already supports this for interfaces blogs.msdn.com/b/typescript/archive/2013/01/24/… "interfaces in TypeScript are open, meaning you can add your own members to an interface by simply writing another interface block." but it currently doesn't for classes. I suspect this is for usability/design reasons rather than .Net metadata being better.
I understand C# partials -- and the lack of actual TypeScript partials. However, the CircleMarker class isn't an interface, so I don't see how it's relevant. The challenge is that interfaces are just compile time checks in TypeScript, so it's possible to "extend" them. However, classes are an actual JavaScript construct, and far more complex to extend cleanly and consistently.
It's relevant because I'm creating a definition for existing JS code, not implementing my own. Ideally the person who made the original definition file should have used interfaces, but they didn't and I'm now stuck trying to work around their design choice. There are various tickets about making classes open like interfaces are, typescript.codeplex.com/workitem/917.

W
WiredPrairie

If you don't control the original definition file, and can't make adjustments to it, then unfortunately, what you're trying to do isn't supported currently in TypeScript. An interface in TypeScript is the only construct that allows reasonable extensions as it is only a compile-time/syntax check and not a run-time operation.

You cannot extend a class in TypeScript with new functionality using only TypeScript (and expecting code-completion/Intellisense to work as expected). You could of course add the functions to the prototype for the CircleMarker class, but they would be unavailable to Intellisense and would fail to compile unless you use a type assertion.

Instead of using any, you should be able to use an interface with the type assertion:

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

Then:

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

Thankfully, it doesn't add any run-time overhead, just a bit of extra typing (pun intended!).

There have been suggestions for things like "mix-ins" on CodePlex, but they have not been implemented. Even the mix-in suggestions would not be entirely straightforward to use, and wouldn't work well for libraries that weren't entirely written in TypeScript (as it would be too easy to have JavaScript code that simply could not be safely constructed for example with a mix-in).


Are there any updates to this answer a year later? Extending the prototypes for previously defined items is something I am really having trouble doing.
No. It's the same behavior.
And now? I heard it's possible to merge types from module declarations, but still haven't found a solution to similar problem
I neeeed this. A DefinitelyTypes lib has an incorrect type for a class, and it'd be nice to fix it by augmenting the definition.
To @trusktr and all who have issues with DefinitelyTyped, patch-package is something that can come in handy if your project uses npm or yarn.
b
basarat

You can't do that with the class keyword. There is a feature request you can vote on here : https://typescript.codeplex.com/workitem/917

You can however mimic classes using interfaces as shown in the workaround (https://typescript.codeplex.com/workitem/917) for the issue. In your case

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

and extend it

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

Unfortunately that would require changing the original definition which isn't directly under my control. This is a good option for those that can easily change the original definition though. Thanks!
FYI, The Feature Request URL is outdated.
R
Roy Yin

This is what I tried and it feels relatively comfortable for me

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

where CircleMarkerEx and ex can be defined as

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

then

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

but it is still a little strange


R
Rune Jeppesen

Is this possible?

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

And then define your CircleMarker instances as MyCircleMarker


Thanks for the reply. It's an option, but it requires the dev to be aware that this special requirement exists. A solution that "just works" would be preferable such as actually extending the class definition.
t
tanguy_k

Edit: this answer is off topic. It talks about interfaces and not classes like asked

I didn't have any problem extending a TypeScript (v3.6) external type definition.

Example with 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!';

You don't have to modify your tsconfig.json: it just works.

It's the way DefinitelyTyped works

See 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 type definition extends webpack type definition. If you use @types/webpack and not @types/webpack-dev-server you won't have the devServer property.

Another example:

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

global.hello = 'Hello, World!';

It's easy enough with interfaces. But I was trying to do it with classes. "In the existing TypeScript definition file the objects are defined as classes rather than interfaces." To my knowledge it's still not possible. Here's a sandbox for you to try: codesandbox.io/s/typescript-playground-n35xm
J
Jack Punt

For those looking for the new/currently documented solution is found in:

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

I have used this to extend the prototype for google-protobuf generated classes to add additional predicates.

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