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

时间:2012-12-19 15:02:43

标签: typescript

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

interface myInterface {
  static Name:string;
}

有可能吗?

16 个答案:

答案 0 :(得分:58)

关注@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 open issue 13462的评论。

视觉效果: 用一些静态方法缺失编译错误。 enter image description here

实现静态方法后,提示方法丢失。 enter image description here

在完成静态接口和普通接口后传递编译。 enter image description here

答案 1 :(得分:36)

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

假设您想要更改Date对象,而不是尝试添加到Date的定义,您可以将其包装,或者只是创建您的丰富日期类来执行{{1}的内容1}}没有。

Date

因为Date是TypeScript中的一个接口,所以你不能使用class RichDate { public static MinValue = new Date(); } 关键字对它进行扩展,这有点遗憾,因为如果date是一个类,这将是一个很好的解决方案。 / p>

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

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

答案 2 :(得分:13)

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

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

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

答案 3 :(得分:12)

您可以正常定义界面:

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

答案 4 :(得分:7)

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

interface MyType {
    instanceMethod();
}

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

答案 5 :(得分:3)

如果您正在寻找定义静态类(即所有方法/属性都是静态的),您可以这样做:

interface MyStaticClassInterface {
  foo():string;
}

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

在这种情况下,静态&#34;类&#34;实际上只是一个简单的-js-object对象,它实现了MyStaticClassInterface

的所有方法

答案 6 :(得分:3)

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

答案 7 :(得分:2)

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

interface myInterface { }

namespace myInterface {
  Name:string;
}

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

答案 8 :(得分:2)

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

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

//------------------------
// 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 :(得分:1)

AFAIK,这不可能。

可悲的是,这不是旧的Java。

答案 10 :(得分:1)

是的,有可能。这是解决方法

$

答案 11 :(得分:1)

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

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

答案 12 :(得分:0)

尽管Typescript接口中不支持static关键字 但是我们可以通过创建具有静态成员的 function interface 来实现。

在下面的代码中,我创建了一个功能接口 Factory ,该接口具有两个静态成员 serialNumber 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();

答案 13 :(得分:0)

我实现了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 :(得分:0)

其他解决方案似乎偏离了祝福之路,我发现我的情况已在我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 :(得分:0)

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

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

然而,经过大约一个小时的修补后,我想我可能刚刚找到了迄今为​​止最好/最干净的解决方案(建立在我最初的想法之上)!如果提出的问题是“如何在接口中包含静态属性?”,那么我认为这是一个相当不错的答案。如果您只需要一个接口(编译时类型/要求/限制),这至少比扩展类要好。对此也没有真正的缺点(好吧,也许是一个小缺点),因为解决方案是 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 确保您不会意外尝试实例化类...

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

这就是它真正的全部内容!

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

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

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

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

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