ChatGPT解决这个技术问题 Extra ChatGPT

如何在 TypeScript 接口中定义静态属性

我只想在打字稿界面中声明一个静态属性?我还没有找到任何关于这个的地方。

interface myInterface {
  static Name:string;
}

可能吗?

你到底想建模什么?
考虑使用“class Foo implements Bar”,其中 Bar 也是一个类。您可以通过 TS 中的其他类“实现”类。

V
Val

遵循@Duncan 的@Bartvds 的回答,在这里提供多年过去后的可行方法。

在 Typescript 1.5 发布后(@Jun 15 '15),您的有用界面

interface MyType {
    instanceMethod();
}

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

可以在装饰器的帮助下以这种方式实现。

/* 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() {}
}

请参阅我在 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 请帮忙,我该如何解决这个问题:'constructor' is declared but its value is never read.ts(6133)
@QuỳnhNguyễn 你可以发布一个详细的问题并分享我的链接
@Val 我正在听从您的提示并在以下位置收到 IDE 警告:return (constructor: T) => {}
@Chklang 谢谢!我用你的建议更新了我的答案
我们如何使用抽象类来做到这一点?
F
Fenton

你不能在 TypeScript 的接口上定义静态属性。

假设您想更改 Date 对象,而不是尝试添加到 Date 的定义中,您可以包装它,或者简单地创建丰富的日期类来完成 Date 不做的事情。

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

因为 Date 是 TypeScript 中的一个接口,所以您不能使用 extends 关键字使用类扩展它,这有点遗憾,因为如果 date 是一个类,这将是一个很好的解决方案。

如果要扩展 Date 对象以在原型上提供 MinValue 属性,您可以:

interface Date {
    MinValue: Date;
}

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

调用使用:

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

如果你想让它在没有实例的情况下可用,你也可以......但它有点挑剔。

interface DateStatic extends Date {
    MinValue: Date;
}

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

调用使用:

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

@Rajagopal 要清楚,您实际上可以使用 extends 关键字在 TS 中扩展接口。您只是不能使用类扩展接口(您需要这样做才能添加静态属性)。
Steve - '因为 Date 是 TypeScript 中的一个接口,你不能使用 extends 关键字来扩展它' - 这是不对的,是吗?
你可以扩展接口,但你不能用一个类来扩展它,只能实现它。我没有说清楚。
我会同意@Nikos 的意见。接口的继承不会破坏增强可读性从而降低可维护性的目的吗?有没有人可以解释为什么使用接口继承而不是新接口?
@TomasHesse 对于那个(第一个)问题没有一个正确的答案。这些决策是竞争需求之间的权衡,正确的答案是在适当的环境中评估权衡。
B
Benny Neugebauer

静态修饰符不能出现在类型成员上(TypeScript 错误 TS1070)。这就是为什么我建议使用抽象类来解决任务:

例子

// 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());

是的,这就是答案!
无法定义抽象静态方法:(
您不能定义抽象静态方法,但可以定义抽象静态属性。以下是如何解决不同错误的一个很好的概述:typescript.tv/error-ts
p
pseudosudo

您可以正常定义接口:

interface MyInterface {
    Name:string;
}

但你不能只做

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

为了表达一个类的静态属性应该遵循这个接口,你需要一些技巧:

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

你甚至可以保留类名,TypeScript (2.0) 不会介意:

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

如果你想静态继承许多接口,你必须先将它们合并成一个新的:

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
}

或者,如果您不想命名合并的接口,您可以执行以下操作:

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
}

工作example


我收到错误 TS2322:匿名类不可分配给类型
@FireCoding 您使用的是哪个 TypeScript 版本?我的示例适用于 2.0
这是比自定义装饰器 @staticImplements 更直接的解决方案。
我认为这是一个很好的解决方案。对于异步静态方法,您还可以使用 reply(msg: IBotMsg): Promise<IBotReply>
有没有办法同时定义静态方法和实例方法,或者现在所有方法都是静态的?
d
duncan

静态属性通常放置在对象的(全局)构造函数中,而“interface”关键字适用于对象的实例。

如果您使用 TypeScript 编写课程,那么前面给出的答案当然是正确的。它可能有助于其他人知道,如果您正在描述一个已经在其他地方实现的对象,那么可以像这样声明包含静态属性的全局构造函数:

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

编辑:作为完整答案发布below
在 Typescript 1.5 发布(6.15.2015)之后的这一点上,这是描述类的静态成员的一种可行方式。 stackoverflow.com/a/43674389/681830
B
Bartvds

@duncan 为静态类型指定 new() 的解决方案也适用于接口:

interface MyType {
    instanceMethod();
}

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

其中哪一个会是我的班级implement
此时您不能使用接口来描述静态成员,只能使用实例成员。因此,在本例中,您的类将实现 MyType(如 class Foo implements MyType)。在描述现有的 JS 代码时,静态接口仅在定义中真正有用。
在 Typescript 1.5 发布(6.15.2015)之后的这一点上,这是描述类的静态成员的一种可行方式。 stackoverflow.com/a/43674389/681830
A
Aleksey L.

此处未提及的另一个选项是使用表示静态接口的类型定义变量并为其分配类表达式:

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

效果与 answer with decorators 相同,但没有装饰器的开销

Playground

GitHub 上的相关 suggestion/discussion


这行得通!我喜欢 Github 上的讨论。 implements static X, static Y 是个好建议。 static 作为每个界面之前的关键字,虽然有点难看,但不应该破坏任何东西。
w
winterhotlatte

解决方案

返回 I 的实例类型并确保 C 扩展 I

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

与实例方法的接口:

interface MyInstance {
    instanceMethod();
}

与静态方法的接口:

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

需要静态方法并使用自己的方法扩展的类:

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

推理

#33892 中讨论了在接口中定义静态方法,#34516 中讨论了抽象静态方法。

根据 Val 和 Aleksey 的回答(谢谢),这个解决方案:

不需要额外的运行时值

类自己的成员信息被保留

允许构造函数约束

测试

原样 - Playground Link

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

如果要从 MyClass - Playground Link 中删除 staticMethod

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'.

如果要从 MyClass - Playground Link 中删除 instanceMethod

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'.

StaticImplements 的返回类型应该是 InstanceType<C>(这也修复了一个错误:“'C' 已声明但它的值从未被读取。”)
@chocolateboy 你是对的,类型检查发生在 C extends I 中,所以返回类型也可能是 object{}。感觉类更适合实际实现目标实例类型,而不是自身或空对象。据我了解,除非设置了 noUnusedParameters,否则它应该只发出警告。在我的例子中,它是 ESLint 突出显示它,禁用它为该行仅在悬停时显示 TS 警告,否则它会变灰。
t
tyler1205

我对顶级答案的复杂程度感到有些惊讶!但也许这只是因为这个线程太老了。

编辑:实际上,经过一些测试,我最初的尝试被证明实际上是无用的,而且这个问题比我最初预期的更难解决。

然而,经过大约一个小时左右的修补,我想我可能刚刚找到了迄今为止最好/最干净的解决方案(基于我最初的想法)!如果提出的问题是“如何在界面中包含静态属性?”,那么我认为这是一个相当不错的答案。如果您只需要一个接口(编译时键入/要求/约束),这至少比扩展一个类要好。这也没有真正的缺点(好吧,也许是一个小缺点),因为解决方案是 100% 环境的(不像某些答案所暗示的基于 extends 的类扩展)并且类是常量(不可变引用,当使用标准的类声明语法而不是我在这里所做的类表达式)。这不会产生运行时开销,也不需要运行时类继承。您可以在一个接口中定义整个类(静态和非静态成员)(环境类用作)!

这是它是如何完成的!

/** ./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;

现在使用接口

/** ./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) {}
};

请注意,typeof 关键字在这里是绝对必要的(如果我没记错的话,那是因为没有它,打字稿认为我们正在指定实例类型,而我们真正想要的是类本身的类型)。例如当我们做

const a: MyClass = new MyClass()

没有 typeof 关键字,我们说 a 应该是一个 MyClass 的实例,而不是 MyClass 本身。

abstract 确保您不会意外尝试实例化类...

编辑:实际上我从我的答案中删除了 abstract 关键字,因为事实证明,真正的类实际上继承了环境类的抽象属性(有意义),因此如果编译器抱怨,则不会实例化提供其类型的环境类被标记为抽象...如果环境类被意外实例化,则只需要处理 ts 不会出错。然后在环境类声明/名称前加上下划线和/或在名称中包含单词 Interface 可能是一个不错的主意,这样它的正确使用就很清楚了(编辑:我已经通过将接口封装在一个文件模块,从而使其对所有其他代码都是私有的,然后只导出对它的类型引用)。

这就是它的全部内容!

将接口放入模块并不是完全必要的,但它提供了一些小好处,包括:

在整个实现代码中使用的“公开”广泛使用的类型注释变得稍微小一些,因为它不再包含关键字 typeof 与包装的环境类/接口声明不同,导出的/面向外部的标识符严格来说是一种类型(别名) ,因此如果有人尝试实例化它或在扩展子句中使用它(或在其他任何需要运行时值的地方使用它),现在将发生错误

在此示例中,我没有为类表达式提供类名,因为类表达式与所有函数表达式一样,如果未提供类名,则只会继承分配给它们的标识符。因此,如果您的标识符与您想要的该类或函数的名称相同,则可以不使用它。或者,您可以像往常一样提供一个内联,它将优先于标识符。类或函数名也可以在函数/类创建后更改,但只能通过 Object.defineProperty 或 Object.defineProperties。

FWIW 类实际上可以是另一个类的 implemented(至少在最新版本的 TS 中),但静态属性无论如何都会被忽略。似乎 implementing 任何东西都只适用于 prototype 在两个方向(到/从)。


e
edan

如果您要定义一个静态类(即所有方法/属性都是静态的),您可以执行以下操作:

interface MyStaticClassInterface {
  foo():string;
}

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

在这种情况下,静态“类”实际上只是一个普通的 ol'-js 对象,它实现了 MyStaticClassInterface 的所有方法


k
ktretyak

您可以merge interface with namespace使用相同的名称:

interface myInterface { }

namespace myInterface {
  Name:string;
}

但是这个接口只有在知道它有属性 Name 时才有用。你不能实现它。


P
Pavel_K

对的,这是可能的。这是解决方案

export interface Foo {

    test(): void;
}

export namespace Foo {

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

}

这太棒了!正是我需要的
@UmarFarooqKhawaja 我很高兴为您提供帮助!
所以这是使用命名空间 Foo 来代替类定义? Class Foo 将实现 statMethod() 这似乎有点像 hack,尤其是因为命名空间现在有点被弃用了......
@dcsan 是的,这有点骇人听闻。但是该怎么办?许多打字稿问题多年来都带有标签 waiting more feedback。所以你要么等到静态方法被添加到接口,要么这样做。
但我也不太明白.. export namespace 实际上是您的 Class 实现?这似乎确实使事情发生了很大变化。我想知道课堂上还有什么其他问题?
i
iffy

我为我的特定用例找到了一种方法(没有装饰器)。

检查静态成员的重要部分是 IObjectClass 并在 createObject 方法中使用 cls: IObjectClass<T>

//------------------------
// 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

这是一个相当简单的方法:

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();

请注意,这不允许您像通常那样使类扩展接口,但在许多情况下这已经足够了。


m
manas

虽然 Typescript 的接口中不支持 static 关键字,但我们可以通过创建一个具有静态成员的 function interface 来实现它。

在下面的代码中,我创建了一个函数接口 Factory,它有两个静态成员 serialNumberprintSerial

// 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

我实现了一个类似 Kamil Szot's 的解决方案,但效果并不理想。我没有足够的声誉将其发布为评论,因此我将其发布在此处,以防有人尝试该解决方案并阅读此内容。

解决方案是:

interface MyInterface {
    Name: string;
}

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

但是,使用类表达式不允许我使用 MyClass 作为类型。如果我写类似:

const myInstance: MyClass;

myInstance 原来是 any 类型,我的编辑器显示以下错误:

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

我最终失去了一个比我想用类的静态部分的接口实现的更重要的类型。

Val's solution 使用装饰器可以避免这个陷阱。


R
Richard Nienaber

其他解决方案似乎偏离了幸福的道路,我发现我的场景包含在我在下面解释的 Typescript documentation 中:

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

我的解决方案非常适合我添加其他静态构造函数的用例。我已经对其进行了测试,并且它通过了所有测试。如果有人发现有问题的边缘案例,请告诉我。

我做了一个泛型类型,它接受接口和静态接口。

它适用于具体和抽象类。

我使用条件类型设计了它,以便所有错误都传播到实现接口的类而不是接口本身。

注意:错误传播允许 vscode 快速修复(实现所有方法)。唯一的缺点是您必须自己应用 static 关键字,因为该错误没有可用的快速修复。

界面:

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

用法:

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

简单的例子

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

这会在 TS 操场上打印“未定义”。您使用的是哪个版本?