ChatGPT解决这个技术问题 Extra ChatGPT

How to define static property in TypeScript interface

I just want to declare a static property in typescript interface? I have not found anywhere regarding this.

interface myInterface {
  static Name:string;
}

Is it possible?

What are you trying to model, exactly?
Consider using ‘class Foo implements Bar’ where Bar is also a class. You can ‘implement’ classes by other classes in TS.

V
Val

Follow @Duncan's @Bartvds's answer, here to provide a workable way after years passed.

At this point after Typescript 1.5 released (@Jun 15 '15), your helpful interface

interface MyType {
    instanceMethod();
}

interface MyTypeStatic {
    new():MyType;
    staticMethod();
}

can be implemented this way with the help of decorator.

/* class decorator */
function staticImplements<T>() {
    return <U extends T>(constructor: U) => {constructor};
}

@staticImplements<MyTypeStatic>()   /* this statement implements both normal interface & static interface */
class MyTypeClass { /* implements MyType { */ /* so this become optional not required */
    public static staticMethod() {}
    instanceMethod() {}
}

Refer to my comment at github issue 13462.

https://i.stack.imgur.com/xV9F7.png

https://i.stack.imgur.com/jummd.png

https://i.stack.imgur.com/awoDo.png


@Val Please help, how I can resolve this issue: 'constructor' is declared but its value is never read.ts(6133)
@QuỳnhNguyễn you can post a question with detail and share me the link
@Val I'm following your tip and got IDE warning at: return (constructor: T) => {}
@Chklang Thank you! I updated my answer with your suggestion
How can we do this with an Abstract class?
F
Fenton

You can't define a static property on an interface in TypeScript.

Say you wanted to change the Date object, rather than trying to add to the definitions of Date, you could wrap it, or simply create your rich date class to do the stuff that Date doesn't do.

class RichDate {
    public static MinValue = new Date();
}

Because Date is an interface in TypeScript, you can't extend it with a class using the extends keyword, which is a bit of a shame as this would be a good solution if date was a class.

If you want to extend the Date object to provide a MinValue property on the prototype, you can:

interface Date {
    MinValue: Date;
}

Date.prototype.MinValue = new Date(0);

Called using:

var x = new Date();
console.log(x.MinValue);

And if you want to make it available without an instance, you also can... but it is a bit fussy.

interface DateStatic extends Date {
    MinValue: Date;
}

Date['MinValue'] = new Date(0);

Called using:

var x: DateStatic = <any>Date; // We aren't using an instance
console.log(x.MinValue);

@Rajagopal To be clear, you can actually extend interfaces in TS using the extends keyword. You just can't extend an interface with a class (which you would need to do in order to add a static property).
Steve - 'Because Date is an interface in TypeScript, you can't extend it using the extends keyword' - that's not right, is it?
You can extend the interface, but you can't extend it with a class, only implement it. I didn't make that clear.
I would second @Nikos opinion. Doesn't inheritance of interfaces defeat the purpose of enhancing readability hence decreasing maintainability? Is there anyone who could give a rational as to why use inheritance of interfaces instead of a new one?
@TomasHesse there isn't a single correct answer to that (first) question. These decisions are trade-offs between competing demands and the correct answer is to assess the trade-off in the appropriate context.
B
Benny Neugebauer

Static modifiers cannot appear on a type member (TypeScript error TS1070). That's why I recommend to use an abstract class to solve the mission:

Example

// Interface definition
abstract class MyInterface {
  static MyName: string;
  abstract getText(): string;
}

// Interface implementation
class MyClass extends MyInterface {
  static MyName = 'TestName';
  getText(): string {
    return `This is my name static name "${MyClass.MyName}".`;
  }
}

// Test run
const test: MyInterface = new MyClass();
console.log(test.getText());

Yep, that's the answer!
Can't define abstract static methods :(
You can't define abstract static methods but you can define abstract static properties. Here is also a great overview of how to solve the different errors: typescript.tv/error-ts
p
pseudosudo

You can define interface normally:

interface MyInterface {
    Name:string;
}

but you can't just do

class MyClass implements MyInterface {
    static Name:string; // typescript won't care about this field
    Name:string;         // and demand this one instead
}

To express that a class should follow this interface for its static properties you need a bit of trickery:

var MyClass: MyInterface;
MyClass = class {
    static Name:string; // if the class doesn't have that field it won't compile
}

You can even keep the name of the class, TypeScript (2.0) won't mind:

var MyClass: MyInterface;
MyClass = class MyClass {
    static Name:string; // if the class doesn't have that field it won't compile
}

If you want to inherit from many interfaces statically you'll have to merge them first into a new one:

interface NameInterface {
    Name:string;
}
interface AddressInterface {
    Address:string;
}
interface NameAndAddressInterface extends NameInterface, AddressInterface { }
var MyClass: NameAndAddressInterface;
MyClass = class MyClass {
    static Name:string; // if the class doesn't have that static field code won't compile
    static Address:string; // if the class doesn't have that static field code won't compile
}

Or if you don't want to name merged interface you can do:

interface NameInterface {
    Name:string;
}
interface AddressInterface {
    Address:string;
}
var MyClass: NameInterface & AddressInterface;
MyClass = class MyClass {
    static Name:string; // if the class doesn't have that static field code won't compile
    static Address:string; // if the class doesn't have that static field code won't compile
}

Working example


I get error TS2322: Anonymous class is not assignable to type
@FireCoding Which TypeScript version are you using? My examples worked with 2.0
This is a more straightforward solution than the custom decorator @staticImplements.
I think this is a great solution. for async static methods you can also use reply(msg: IBotMsg): Promise<IBotReply>
is there a way to define both static and instance methods or are all methods now static only?
d
duncan

Static properties are usually placed on the (global) constructor for the object, whereas the "interface" keyword applies to instances of the object.

The previous answer given is of course correct if you are writing the class in TypeScript. It may help others to know that if you are describing an object that is already implemented elsewhere, then the global constructor including static properties can be declared like this:

declare var myInterface : {
  new(): Interface;
  Name:string;
}

edit: posted as a full answer below
At this point after Typescript 1.5 released (6.15.2015), here it is a workable way to describe static members of a class. stackoverflow.com/a/43674389/681830
B
Bartvds

@duncan's solution above specifying new() for the static type works also with interfaces:

interface MyType {
    instanceMethod();
}

interface MyTypeStatic {
    new():MyType;
    staticMethod();
}

Which one of these would my class implement?
At this point you cannot use an interface to describe static members, only instance members. So in this example your class would implement MyType (as in class Foo implements MyType). The static interface is only real useful in definitions, when describing existing JS code.
At this point after Typescript 1.5 released (6.15.2015), here it is a workable way to describe static members of a class. stackoverflow.com/a/43674389/681830
A
Aleksey L.

Another option not mentioned here is defining variable with a type representing static interface and assigning to it class expression:

interface MyType {
    instanceMethod(): void;
}

interface MyTypeStatic {
    new(): MyType;
    staticMethod(): void;
}

// ok
const MyTypeClass: MyTypeStatic = class MyTypeClass {
    public static staticMethod() { }
    instanceMethod() { }
}

// error: 'instanceMethod' is missing
const MyTypeClass1: MyTypeStatic = class MyTypeClass {
    public static staticMethod() { }
}

// error: 'staticMethod' is missing
const MyTypeClass2: MyTypeStatic = class MyTypeClass {
    instanceMethod() { }
}

The effect is same as in answer with decorators, but without overhead of decorators

Playground

Relevant suggestion/discussion on GitHub


This works! I liked the discussion on Github. implements static X, static Y is a good suggestion. static as a keyword before every interface should not be breaking anything though a bit ugly.
w
winterhotlatte

Solution

Returns an instance type of I and makes sure C extends I:

type StaticImplements<I extends new (...args: any[]) => any, C extends I> = InstanceType<I>;

Interface with instance method:

interface MyInstance {
    instanceMethod();
}

Interface with static method:

interface MyClassStatic {
    new (...args: any[]): MyInstance;
    staticMethod();
}

Class requiring static method and extending with its own method:

class MyClass implements StaticImplements<MyClassStatic, typeof MyClass> {
    static staticMethod();
    static ownStaticMethod();
    instanceMethod();
    ownInstanceMethod();
}

Reasoning

Defining static methods in interfaces is being discussed in #33892, and abstract static methods are being discussed in #34516.

Based on Val's and Aleksey's answers (thank you), this solution:

Does not require additional runtime values

Class own member information is preserved

Allows constructor constraints

Test

As is - Playground Link:

MyClass.staticMethod(); // OK
MyClass.ownStaticMethod(); // OK
new MyClass().instanceMethod(); // OK
new MyClass().ownInstanceMethod(); // OK

If to remove staticMethod from MyClass - Playground Link:

class MyClass implements StaticImplements<MyClassStatic, typeof MyClass> {} // Type 'typeof MyClass' does not satisfy the constraint 'MyClassStatic'. Property 'staticMethod' is missing in type 'typeof MyClass' but required in type 'MyClassStatic'.

If to remove instanceMethod from MyClass - Playground Link:

class MyClass implements StaticImplements<MyClassStatic, typeof MyClass> {} // Class 'MyClass' incorrectly implements interface 'MyInstance'. Property 'instanceMethod' is missing in type 'MyClass' but required in type 'MyInstance'.

The return type of StaticImplements should be InstanceType<C> (this also fixes an error: "'C' is declared but its value is never read.")
@chocolateboy you are right, the type check happens in C extends I, so return type might as well be object or {}. It just feels more appropriate for class to actually implement the target instance type, rather than itself or empty object. To my understanding, unless noUnusedParameters is set, it should emit only a warning. In my case, it's ESLint that highlights it, disabling it for that line shows the TS warning only on hover, otherwise it's greyed out.
t
tyler1205

I'm somewhat surprised at how overly complicated the top answers are! But maybe that's just because this thread is so old.

Edit: Actually my initial attempt proved to be practically useless after some testing, and this problem turned out to be somewhat harder to tackle than I had originally expected.

After about an hour or so of tinkering however, I think I may have just found the best/cleanest solution so far (building upon my initial idea)! If the question posed is "How do I include static properties in an interface?", then I think this is a fairly decent answer. This is at least better than extending a class if all you need is an interface (compiletime typing/requirements/restraints). There's no real drawback (well, maybe one small one) to this either since the solution is 100% ambient (unlike extends-based class extension like some answers are suggesting) and classes are constants regardless (immutable references that are not hoisted when using the standard class declaration syntax instead of a class expression as I'm doing here). This incurs no runtime overhead and doesn't require runtime class inheritance. You can define the entire class (both static and non-static members) all in one (ambient class used as an) interface!

Here is how it's done!

/** ./interface.ts */
// In a file module (best), or wrap in ts module or namespace block

// Putting this in a module provides encapsulation ensuring that no one is
// at risk of misusing this class (it must be used as a type only). 

// Export only a type reference which will make it error is someone tries 
// to use it as a value (such as in an `extends` clause, or trying to 
// instantiate it).

/** 
 * Other Ideas For Names To Differentiate From Actual Classes/Non-Ambient Values:
 * MyClassInterface or _Interface_MyClass or MyClass_Interface or Interface_MyClass  
 **/
declare class _MyClassInterface {
    static staticProp: string;
    static staticMethod(): number;
    readonly prop: boolean 
    /** 
     * Note: Above, readonly won't need to be specified in the real class 
     * but the prop *will* still be readonly anyway.
     *
     * Now for the only caveat!
     * It does appear however that you cannot mark anything private or 
     * protected in this pseudo-interface which is a bummer, only props
     * and methods that appear only in the real class can be.
     */
    prop2: boolean;
    method(): Function;
    constructor(p1: string, p2: number);
}

export type MyClassInterface = typeof _MyClassInterface;

now to consume the interface

/** ./consumer.ts */
import { MyClassInterface } from "./interface" // type import

const MyClass: MyClassInterface = class {
    static staticProp: string;
    prop: boolean;
    prop2: boolean;
    protected onlyOnRealClass: boolean; /* this is ok since this prop doesn't exist on the interface */

    static staticMethod() {
        return 5;
    }

    method() {
        return () => {};
    }

    constructor(p1: string, p2: number) {}
};

note that the typeof keyword is absolutely essential here (if I recall correctly, it is because without it typescript thinks we are specifying the instance type when what we really want is the type of the class itself). For example when we do

const a: MyClass = new MyClass()

without the typeof keyword, we're saying that a should be an instance of MyClass, not MyClass itself.

abstract ensures you don't accidentally try to instantiate the class...

Edit: Actually I'm removing the abstract keyword from my answer because it turns out that the real class actually inherits the property of being abstract from the ambient class (makes sense), and will therefore not instantiate w/o the compiler complaining if the ambient class that provides its type is marked abstract... just gonna have to deal with ts not erroring if the ambient class is ever accidentally instantiated. It might be a decent idea then to prefix the ambient class declaration/name with an underscore and/or include the word Interface in the name so its proper use is clear (edit: I have since tackled this issue by encapsulating the interface in a file module thereby rendering it private to all other code and then exporting only a type reference to it).

and that is all there is to it really!

Putting the interface into a module isn't totally necessary but it provides a couple small benefits including:

the "publicly" widely-used type-annotation that is used throughout implementation code becomes slightly smaller since it no longer includes the keyword typeof unlike the wrapped ambient class/interface declaration, the exported/outward-facing identifier is strictly a type (alias), so an error will now occur if anyone tries to instantiate it or use it in an extends clause (or use it anywhere else a runtime value is expected)

I'm not supplying a class name for the Class Expression in this example because Class Expressions, like all Function Expressions, will just inherit the identifier that they are assigned to if a class name if not provided. So if your identifier is identical to the name you want for that class or function anyways, you can just leave it off. Or, you may provide one inline as usual and it will take precedence over the identifier. A class or function name can also be changed after function/class creation, but only via Object.defineProperty or Object.defineProperties.

FWIW classes can actually be implemented (at least in recent versions of TS) by another class, but the static properties will just be ignored anyways. It seems that implementing anything only applies to the prototype in both directions (to/from).


e
edan

If you're looking to define a static class (ie. all methods/properties are static), you can do something like this:

interface MyStaticClassInterface {
  foo():string;
}

var myStaticClass:MyStaticClassInterface = {
  foo() {
    return 'bar';
  }
};

In this case, the static "class" is really just a plain-ol'-js-object, which implements all the methods of MyStaticClassInterface


k
ktretyak

You can merge interface with namespace using the same name:

interface myInterface { }

namespace myInterface {
  Name:string;
}

But this interface is only useful to know that its have property Name. You can not implement it.


P
Pavel_K

Yes, it is possible. Here is the solution

export interface Foo {

    test(): void;
}

export namespace Foo {

    export function statMethod(): void {
        console.log(2);
    }

}

This is awesome! Exactly what I needed
@UmarFarooqKhawaja I am glad to help!
so this is using a namespace Foo to stand in for a class definition? where Class Foo would implement the statMethod() this seems a bit of a hack, esp since namespaces are kind of deprecated now...
@dcsan Yes, it is a bit of a hack. But what to do? A lot of issues of typescript are open for many years with label waiting more feedback. So you either wait until static methods are added to interface or do this way.
but also i don't quite get it.. the export namespace is actually your Class implementation? That does seem to be bending things a lot. I wonder what else that breaks in Classes?
i
iffy

I found a way to do this (without decorators) for my specific use case.

The important part that checks for static members is IObjectClass and using cls: IObjectClass<T> in the createObject method:

//------------------------
// Library
//------------------------
interface IObject {
  id: number;
}
interface IObjectClass<T> {
  new(): T;
  table_name: string;
}
function createObject<T extends IObject>(cls: IObjectClass<T>, data:Partial<T>):T {
  let obj:T = (<any>Object).assign({},
    data,
    {
      id: 1,
      table_name: cls.table_name,
    }
  )
  return obj;
}

//------------------------
// Implementation
//------------------------
export class User implements IObject {
  static table_name: string = 'user';
  id: number;
  name: string;
}

//------------------------
// Application
//------------------------
let user = createObject(User, {name: 'Jimmy'});
console.log(user.name);

M
Merlin04

Here's a fairly simple way to do it:

interface MyClass {
    new (): MyClassInstance;
    staticMethod(): string;
}

interface MyClassInstance {
    instanceMethod(): string;
}

const Class: MyClass = class {
    static staticMethod() {
        return "This is a static method";
    }
    instanceMethod() {
        return "This is an instance method";
    }
}

Class.staticMethod();

// Has type MyClassInstance
const instance = new Class();
instance.instanceMethod();

Note that this doesn't allow you to make a class extend an interface like you could normally, but for many situations this is good enough.


m
manas

Though static keyword not supported in interface in Typescript but we can achieve it by creating a function interface that has static member.

In my following code I have created a function interface Factory that has two static members serialNumber and printSerial.

// factory is a function interface
interface Factory<T> {
    (name: string, age: number): T;

    //staic property
    serialNumber: number;

    //static method
    printSrial: () => void;
}

class Dog {
    constructor(public name: string, public age: number) { }
}

const dogFactory: Factory<Dog> = (name, age) => {
    return new Dog(name, age);
}

// initialising static members

dogFactory.serialNumber = 1234;
dogFactory.printSrial = () => console.log(dogFactory.serialNumber);


//instance of Dog that DogFactory creates
const myDog = dogFactory("spike", 3);

//static property that returns 1234
console.log(dogFactory.serialNumber)

//static method that prints the serial 1234
dogFactory.printSrial();

D
Dario Scattolini

I implemented a solution like Kamil Szot's, and it has an undesired effect. I have not enough reputation to post this as a comment, so I post it here in case someone is trying that solution and reads this.

The solution is:

interface MyInterface {
    Name: string;
}

const MyClass = class {
    static Name: string;
};

However, using a class expression doesn't allow me to use MyClass as a type. If I write something like:

const myInstance: MyClass;

myInstance turns out to be of type any, and my editor shows the following error:

'MyClass' refers to a value, but is being used as a type here. Did you mean 'typeof MyClass'?ts(2749)

I end up losing a more important typing than the one I wanted to achieve with the interface for the static part of the class.

Val's solution using a decorator avoids this pitfall.


R
Richard Nienaber

The other solutions seem to stray from the blessed path and I found that my scenario was covered in the Typescript documentation which I've paraphrased below:

interface AppPackageCheck<T> {
  new (packageExists: boolean): T
  checkIfPackageExists(): boolean;
}

class WebApp {
    public static checkIfPackageExists(): boolean {
        return false;
    }

    constructor(public packageExists: boolean) {}
}

class BackendApp {
    constructor(public packageExists: boolean) {}
}

function createApp<T>(type: AppPackageCheck<T>): T {
    const packageExists = type.checkIfPackageExists();
    return new type(packageExists)
}

let web = createApp(WebApp);

// compiler failure here, missing checkIfPackageExists
let backend = createApp(BackendApp); 

H
HeadJk

My solution has worked great for my use case of adding additional static constructors. I have tested it and it has passed all tests. If anyone finds a buggy edge case please let me know.

I made a generic type which takes in the interface and static interface.

It works for concrete and abstract classes.

I have designed it using conditional types such that all errors get propagated to the class implementing the Interface rather than the interface itself.

Note: The error propagation allows for vscode quick fixes (implement all methods). The only drawback is you will have to apply the static keyword yourself because there is no quick fix available for that error.

The Interface:

type Class<T = any> = new (...args: any[]) => T;
type AbstractClass<T = any> = abstract new (...args: any[]) => T;

type Interface<C extends Class<InstanceType<C>> | AbstractClass<InstanceType<C>>, SI, I = {}> = 
    C extends Class<InstanceType<C>> 
    // ConcreteClass
    ? InstanceType<C> extends I 
        ? C extends (SI & Class<InstanceType<C>>)
            ? (InstanceType<C> & I)
            : (SI & Class<InstanceType<C>>) // Indicate StaticInterface Error
        : I // Indicate Interface Error
    // AbstractClass
    : InstanceType<C> extends I 
        ? C extends (SI & AbstractClass<InstanceType<C>>)
            ? (InstanceType<C> & I)
            : (SI & AbstractClass<InstanceType<C>>) // Indicate StaticInterface Error
        : I // Indicate Interface Error

Usage:

interface MyInterface {
    instanceMethod(): number;
}

interface MyStaticInterface {
    staticMethod(): number;
}
 
class MyClass implements Interface<typeof MyClass, MyStaticInterface, MyInterface> {
    static staticMethod(): number {
        return 50;
    }

    instanceMethod(): number {
        return 100;
    }

    static otherStatic() {
        return "HELLO"
    }

    otherInstance() {
        return "GOODBYE"
    }
}

abstract class MyClass1 implements Interface<typeof MyClass1, MyStaticInterface, MyInterface> {
    static staticMethod(): number {
        return 50;
    }


    instanceMethod(): number {
        return 20;
    }

    static otherStatic() {
        return "HELLO"
    }

    otherInstance() {
        return "GOODBYE"
    }

    abstract abstractMethod() : number;

}

C
Cleiton

Simple example

interface Person {
  name: string;
  age: number;
}

abstract class Trackable {
  static TrackInstances: number;
}

class Pablo extends Trackable implements Person {
  constructor(public name: string, public age: number) { Pablo.TrackInstances+=1; }
}
console.log(Pablo.TrackInstances);

This prints "undefined" in the TS playground. Which version are you using?