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

Raj*_*l 웃 94 typescript

我只想在typescript接口中声明一个静态属性?关于这个,我没有找到任何地方.

interface myInterface {
  static Name:string;
}
Run Code Online (Sandbox Code Playgroud)

可能吗?

Val*_*Val 79

关注@Duncan的@ Bartvds的回答,这里提供了一个可行的方法,经过多年的过去.

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

interface MyType {
    instanceMethod();
}

interface MyTypeStatic {
    new():MyType;
    staticMethod();
}
Run Code Online (Sandbox Code Playgroud)

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

/* 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() {}
}
Run Code Online (Sandbox Code Playgroud)

请参阅github open issue 13462上的评论.

可视化结果:编译错误,提示缺少静态方法. 在此输入图像描述

实现静态方法后,提示方法丢失. 在此输入图像描述

在完成静态接口和普通接口之后传递编译. 在此输入图像描述

  • 你的答案就是答案.非常感谢.使用Typescript 2.4.1 (5认同)
  • 我们如何使用抽象类来做到这一点? (5认同)
  • @mmmeff我用3.0.1测试过它的工作原理.你的意思是"不工作"?有没有错误信息? (2认同)

Fen*_*ton 38

您无法在TypeScript中的接口上定义静态属性.

假设您想要更改Date对象,而不是尝试添加到定义中Date,您可以将其包装,或者只是创建您的丰富日期类来执行Date不能执行的操作.

class RichDate {
    public static MinValue = new Date();
}
Run Code Online (Sandbox Code Playgroud)

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

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

interface Date {
    MinValue: Date;
}

Date.prototype.MinValue = new Date(0);
Run Code Online (Sandbox Code Playgroud)

叫做使用:

var x = new Date();
console.log(x.MinValue);
Run Code Online (Sandbox Code Playgroud)

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

interface DateStatic extends Date {
    MinValue: Date;
}

Date['MinValue'] = new Date(0);
Run Code Online (Sandbox Code Playgroud)

叫做使用:

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

  • @Rajagopal 需要明确的是,您实际上可以使用 `extends` 关键字在 TS 中扩展接口。您只是无法使用类扩展接口(您需要这样做才能添加静态属性)。 (3认同)
  • 我实际上更喜欢使用多个接口而不是扩展接口. (2认同)

win*_*tte 18

解决方案

返回 的实例类型I,确保I是有效的具体构造函数并C扩展I

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

或者是最通用的版本,应该适用于更多情况。返回any并确保C扩展I

type StaticImplements<I, C extends I> = any;
Run Code Online (Sandbox Code Playgroud)

与实例方法的接口:

interface MyInstance {
    instanceMethod();
}
Run Code Online (Sandbox Code Playgroud)

具有静态方法的接口:

interface MyClassStatic {
    new (...args: any[]): MyInstance;
    staticMethod();
}
Run Code Online (Sandbox Code Playgroud)

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

class MyClass implements StaticImplements<MyClassStatic, typeof MyClass> {
    static staticMethod();
    static ownStaticMethod();
    instanceMethod();
    ownInstanceMethod();
}
Run Code Online (Sandbox Code Playgroud)

笔记

  • 类型检查实际上发生在C extends I
  • 参数类型的约束I可以根据需要更改
  • 返回类型是类最终通过implements;实现的类型 For InstanceType<I>class 实际上会实现一个实例类型,尽管object, {}orany应该可以工作

推理

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

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

  • 不需要额外的运行时值
  • 类自己的成员信息被保留
  • 允许构造函数约束

测试

按原样 - Playground 链接

MyClass.staticMethod(); // OK
MyClass.ownStaticMethod(); // OK
new MyClass().instanceMethod(); // OK
new MyClass().ownInstanceMethod(); // OK
Run Code Online (Sandbox Code Playgroud)

如果要从- Playground 链接staticMethod中删除:MyClass

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'.
Run Code Online (Sandbox Code Playgroud)

如果要从- Playground 链接instanceMethod中删除:MyClass

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'.
Run Code Online (Sandbox Code Playgroud)


小智 14

静态属性通常放在对象的(全局)构造函数上,而"interface"关键字则应用于对象的实例.

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

declare var myInterface : {
  new(): Interface;
  Name:string;
}
Run Code Online (Sandbox Code Playgroud)


Kam*_*zot 14

您可以正常定义界面:

interface MyInterface {
    Name:string;
}
Run Code Online (Sandbox Code Playgroud)

但你不能这样做

class MyClass implements MyInterface {
    static Name:string; // typescript won't care about this field
    Name:string;         // and demand this one instead
}
Run Code Online (Sandbox Code Playgroud)

要表示类应该遵循此接口的静态属性,您需要一些技巧:

var MyClass: MyInterface;
MyClass = class {
    static Name:string; // if the class doesn't have that field it won't compile
}
Run Code Online (Sandbox Code Playgroud)

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

var MyClass: MyInterface;
MyClass = class MyClass {
    static Name:string; // if the class doesn't have that field it won't compile
}
Run Code Online (Sandbox Code Playgroud)

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

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
}
Run Code Online (Sandbox Code Playgroud)

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

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
}
Run Code Online (Sandbox Code Playgroud)

工作实例


Ben*_*uer 14

静态修饰符不能出现在类型成员上(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());
Run Code Online (Sandbox Code Playgroud)

  • 无法定义抽象静态方法:( (7认同)
  • 您不能定义抽象静态方法,但可以定义抽象静态属性。这里还概述了如何解决不同的错误:https://typescript.tv/error-ts/ (2认同)
  • @BennyCode 不,你不能。 (2认同)

Bar*_*vds 7

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

interface MyType {
    instanceMethod();
}

interface MyTypeStatic {
    new():MyType;
    staticMethod();
}
Run Code Online (Sandbox Code Playgroud)

  • 此时,您不能使用接口来描述静态成员,而只能描述实例成员。因此,在此示例中,您的类将实现`MyType`(如在类Foo中实现MyType`)。当描述现有的JS代码时,静态接口仅在定义中真正有用。 (2认同)

tmi*_*llr 7

我对最重要的答案过于复杂感到有点惊讶!但也许这只是因为这个线程太旧了。

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

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

现在使用接口

/** ./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) {}
};
Run Code Online (Sandbox Code Playgroud)

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

const a: MyClass = new MyClass()
Run Code Online (Sandbox Code Playgroud)

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

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

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

这就是它的全部内容了!

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

  1. 在整个实现代码中使用的“公开”广泛使用的类型注释变得稍微小一些,因为它不再包含关键字typeof

  2. 与包装的环境类/接口声明不同,导出/向外的标识符严格来说是一种类型(别名),因此如果有人尝试实例化它或在扩展子句中使用它(或在其他任何地方使用它),现在就会发生错误预期运行时值)

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

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


Ano*_*ken 7

Winterhotlatte有一个很好的答案,让我走上了正确的道路。\n
\n但是,强制在静态接口中包含构造函数非常不方便。

\n

简化版

\n

反转扩展给出:

\n
type Static<TClass extends IStaticInterface & { new(...args) }, IStaticInterface>\n  = InstanceType<TClass>;\n
Run Code Online (Sandbox Code Playgroud)\n

无需向接口添加构造函数:

\n
interface IMeow { readonly IsMeow: boolean; }\n
Run Code Online (Sandbox Code Playgroud)\n

并且使用方便,如下所示:

\n
class Cat implements Static<typeof Cat, IMeow> {\n  readonly static IsMeow = true;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

就像之前一样,如果 中static IsMeow缺少,它会给出一个非常明显的错误Cat。 \n
\n也像之前一样,在其他方面它仍然可以正常工作。

\n

精美的可读性(主观

\n

需要类型后有一个“implements”字符串:

\n
type Static<TClass extends IStaticInterface & { new(...args) },\n  _txt extends "implements", IStaticInterface>\n  = InstanceType<TClass>;\n
Run Code Online (Sandbox Code Playgroud)\n

这里再次展示我们的猫:

\n
class Cat implements Static<typeof Cat, "implements", IMeow> {\n  static readonly IsMeow = true;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

合并多个(过多

\n

这确实不需要,你可以重复Static<...>,但这里是:

\n
type Static<TClass extends InterfacesCombined & { new(...args) }, _txt extends "implements",\n  IStaticInterface, IStatic2 = {}, IStatic3 = {}, IStatic4 = {}, IStatic5 = {},\n  InterfacesCombined extends IStaticInterface & IStatic2 & IStatic3 & IStatic4 & IStatic5\n    = IStaticInterface & IStatic2 & IStatic3 & IStatic4 & IStatic5>\n  = InstanceType<TClass>;\n
Run Code Online (Sandbox Code Playgroud)\n

为了演示,让我们以更复杂的方式升级我们的 Cat:

\n
interface IPurr { purr(): string; }\ninterface ILick { Lick(human: any): void; }\n
Run Code Online (Sandbox Code Playgroud)\n

用法如下:

\n
class Cat implements IPurr, Static<typeof Cat, "implements", IMeow, ILick> {\n  static readonly IsMeow = true;\n  static Lick(human: any) { /* obey me homan! */ }\n  \n  purr() { return "Prrrrr"; }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

具体的静态接口

\n

如果你经常使用静态接口,你不想输入所有这些Static<...东西。\n
\n首先让这个小助手离开:

\n
type New<T> = { new(...args: any): T };\n
Run Code Online (Sandbox Code Playgroud)\n

现在让我们“烘焙”一个静态ILick界面:

\n
type IStaticLick<TClass extends ILick & New<InstanceType<TClass>> = InstanceType<TClass>;\n
Run Code Online (Sandbox Code Playgroud)\n

瞧\xc3\xa1:

\n
class Cat2 implements IStaticLick<typeof Cat2> {\n  static Lick(human: any) { /* obey me homan! */ }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

到底发生了什么?

\n

我们只是要求typeof T实现一些东西,使其成为我们的“静态接口”的有效参数。

\n

因此,如果interface IFoo { stuff }+class Foo implements IFoo表示Foo is "stuff",那么我们所说的是"stuff" must be in T for T to be allowed inside Static<T, ...>

\n

所以implements的部分class Foo implements Static<...>并没有真正说明 Foo 有什么特别之处。而是以一种迂回的方式,我们只是说我们有这些花哨的<支架>,里面是一个只接受“东西”滚轮的 VIP 俱乐部!

\n

换句话说我们可以写:

\n
class FakeCat implements IStaticLick<typeof Cat2> { }\n
Run Code Online (Sandbox Code Playgroud)\n

……明白我的意思了吗?

\n

Static<TClass, ...>我们尝试通过要求实际实现 的实例类型来稍微减轻这个明显的问题TClassInstanceType<TClass>但是,如果没有任何实例成员(例如我们的类) ,则这根本不会执行任何操作Cat2

\n

FakeCat 确实实现了Cat2- 因为实现起来并不难:{ }

\n

演示

\n

游乐场链接

\n


Ale*_* L. 6

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

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() { }
}
Run Code Online (Sandbox Code Playgroud)

效果与装饰器的回答相同,但没有装饰器的开销

操场

GitHub上的相关建议/讨论