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

Posted

技术标签:

【中文标题】如何在 TypeScript 接口中定义静态属性【英文标题】:How to define static property in TypeScript interface 【发布时间】:2012-12-06 23:15:09 【问题描述】:

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

interface myInterface 
  static Name:string;

有可能吗?

【问题讨论】:

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

您不能在 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 对于那个(第一个)问题没有一个正确的答案。这些决策是相互竞争的需求之间的权衡,正确的答案是在适当的环境中评估权衡。【参考方案2】:

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

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

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

【讨论】:

编辑:作为完整答案发布below 在 Typescript 1.5 发布后(6.15.2015),这里是描述类的静态成员的可行方法。 ***.com/a/43674389/681830【参考方案3】:

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

interface MyType 
    instanceMethod();


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

【讨论】:

我的班级implement会是哪一个? 此时不能使用接口来描述静态成员,只能使用实例成员。所以在这个例子中,你的类将实现MyType(如class Foo implements MyType)。静态接口仅在定义中真正有用,在描述现有的 JS 代码时。 在 Typescript 1.5 发布后(6.15.2015),这里是描述类的静态成员的可行方法。 ***.com/a/43674389/681830【参考方案4】:

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

interface MyStaticClassInterface 
  foo():string;


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

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

【讨论】:

【参考方案5】:

可以正常定义接口:

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&lt;IBotReply&gt; 有没有办法同时定义静态和实例方法,或者现在所有方法都只是静态的?【参考方案6】:

你可以merge interface with namespace使用相同的名字:

interface myInterface  

namespace myInterface 
  Name:string;

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

【讨论】:

【参考方案7】:

按照@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 的评论。

视觉结果: 编译错误提示缺少静态方法。

实现静态方法后,提示缺少方法。

在静态接口和普通接口都完成后编译通过。

【讨论】:

@Val 请帮忙,我该如何解决这个问题:'constructor' is declared but its value is never read.ts(6133) @QuỳnhNguyễn 你可以发布一个详细的问题并将链接分享给我 @Val 我正在听从您的提示并在以下位置收到 IDE 警告:return (constructor: T) =&gt; @Chklang 谢谢!我用你的建议更新了我的答案 我们如何使用抽象类来做到这一点?【参考方案8】:

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

检查静态成员的重要部分是IObjectClass,并在createObject 方法中使用cls: IObjectClass&lt;T&gt;

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

【讨论】:

【参考方案9】:

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

export interface Foo 

    test(): void;


export namespace Foo 

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


【讨论】:

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

静态修饰符不能出现在类型成员上(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【参考方案11】:

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

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

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

【讨论】:

【参考方案12】:

这里没有提到的另一个选项是使用表示静态接口的类型定义变量并为其分配类表达式:

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 作为每个界面之前的关键字不应该破坏任何东西,虽然有点难看。【参考方案13】:

我实现了一个类似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 使用装饰器可以避免这个陷阱。

【讨论】:

【参考方案14】:

其他解决方案似乎偏离了幸福的道路,我发现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); 

【讨论】:

【参考方案15】:

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

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

然而,经过大约一个小时左右的修补,我想我可能刚刚找到了迄今为​​止最好/最干净的解决方案(基于我最初的想法)!如果提出的问题是“如何在界面中包含静态属性?”,那么我认为这是一个相当不错的答案。如果您只需要一个接口(编译时键入/要求/约束​​),这至少比扩展一个类要好。这没有真正的缺点(好吧,也许是一个小缺点),因为解决方案是 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 在两个方向(到/从)。

【讨论】:

【参考方案16】:

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

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

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

【讨论】:

【参考方案17】:

解决方案

返回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 中删除staticMethod - 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'.

如果要从MyClass 中删除instanceMethod - 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'.

【讨论】:

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

以上是关于如何在 TypeScript 接口中定义静态属性的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 TypeScript 在接口中定义对象的命名数组?

访问 TypeScript 中默认无名类中的静态属性

如何在 Typescript 接口中存储 objectID 属性?

TypeScript接口

TypeScript教程# 13:接口

Typescript中的类