TypeScript静态类


145

我想从传统的JS迁移到TypeScript,因为我喜欢类似C#的语法。我的问题是我找不到如何在TypeScript中声明静态类。

在C#中,我经常使用静态类来组织变量和方法,将它们放在一起命名的类中,而无需实例化对象。在香草JS中,我曾经用一个简单的JS对象来做到这一点:

var myStaticClass = {
    property: 10,
    method: function(){}
}

在TypeScript中,我宁愿使用C语言尖锐的方法,但似乎TS中不存在静态类。什么是解决此问题的合适方法?

Answers:


166

TypeScript不是C#,因此您不必期望TypeScript中的C#概念相同。问题是为什么您要使用静态类?

在C#中,静态类只是不能被子类化的类,只能包含静态方法。C#不允许在类之外定义函数。但是,在TypeScript中这是可能的。

如果您正在寻找一种将函数/方法放在命名空间(即非全局)中的方法,则可以考虑使用TypeScript的模块,例如

module M {
    var s = "hello";
    export function f() {
        return s;
    }
}

因此,您可以从外部访问Mf(),但不能访问s,并且不能扩展模块。

有关更多详细信息,请参见TypeScript 规范


所以一个模块可以有一个静态方法,但是一个类不能?但是模块不能包含静态数据。它不像JS那样方便,无需实例化任何内容即可包装数据和代码。
dcsan

这可能是有用的,包括你需要包括.jshtml。因此,对于Angular 2你可能使用System... so'd做System.import("Folder/M");(或任何路径是编译的.js文件)的前bootstrap import
Serj萨根

11
不推荐使用。此外,tslint将不再允许您对模块和名称空间执行此操作。在这里阅读:palantir.github.io/tslint/rules/no-namespace
Florian Leitgeb

我有一个使用静态类的用例。现在,我有一个仅包含静态方法的类。在项目中,我们需要为每个可以实例化的类提供某种配置。将此类声明为static不仅可以使我注意到它不会被实例化,而且可以避免其他开发人员向其添加实例成员。
mdarefull

@florian leitgeb那么,只有静态方法和/或abstract关键字的类的首选方式是什么?相较于现在似乎已弃用的模块,这似乎
简直糟透了

180

自TypeScript 1.6以来,抽象类一直是TypeScript的一等公民。您不能实例化一个抽象类。

这是一个例子:

export abstract class MyClass {         
    public static myProp = "Hello";

    public static doSomething(): string {
      return "World";
    }
}

const okay = MyClass.doSomething();

//const errors = new MyClass(); // Error

6
在处理静态类时,这是最好的响应,我对此表示赞同。Singleton用于同一类实例的共享内存模式。另外,静态类没有定义的实例,因此,如果客户端尝试对其进行初始化,则必须抛出异常。
loretoparisi

1
我们可以做一个抽象类吗?
KimchiMan'7

@KimchiMan-是的,abstractTypeScript支持该关键字。
Fenton

1
更新使用abstract!@KimchiMan-好主意!
黑曜石

3
这比将构造方法标记为私有更好吗?
Joro Tenev '18

72

Typescript语言规范的 8.2.1中描述了定义类的静态属性和方法的方法:

class Point { 
  constructor(public x: number, public y: number) { } 
  public distance(p: Point) { 
    var dx = this.x - p.x; 
    var dy = this.y - p.y; 
    return Math.sqrt(dx * dx + dy * dy); 
  } 
  static origin = new Point(0, 0); 
  static distance(p1: Point, p2: Point) { 
    return p1.distance(p2); 
  } 
}

在哪里Point.distance()是静态(或“类”)方法。


14
这显示了如何创建静态方法,它没有回答关于静态的问题(除非真正的问题实际上是关于静态方法的问题)。
Marcus 2012年

18
感谢您的评论。它描述了如何创建静态属性和方法,这些方法和方法共同使一个类可以创建具有数据和功能的类,而无需实例化。尽管不是专门的“静态类”,但它确实满足了OP的JavaScript示例所描述的要求。
罗布·赖斯

C#直到版本2才具有静态类。它们仅存在于c#中,只是为了防止实例化它们。你不能做到这一点与JavaScript,因此没有多大意义
Simon_Weaver

4
@Simon_Weaver您不能在javascript中做什么?防止实例化类?无论如何,Typescript并不在乎运行时,只要您在尝试做某些事情时遇到编译错误,就不会允许我们这样做。
亚历克斯

我猜我的意思是,直到版本2为止,“我们在类级别上没有静态KEYWORD(这意味着您无法创建类的实例)”,但是再次阅读它,我认为我的评论有点遗漏了反正点。OP并不是真正为整个班级寻找“静态关键字”
Simon_Weaver

20

这个问题已经过时,但我想留下一个利用当前语言版本的答案。不幸的是,静态类在TypeScript中仍然不存在,但是您可以使用私有构造函数编写一个行为类似,但开销很小的类,以防止从外部实例化类。

class MyStaticClass {
    public static readonly property: number = 42;
    public static myMethod(): void { /* ... */ }
    private constructor() { /* noop */ }
}

该代码片段将允许您使用类似于C#对应对象的“静态”类,唯一的缺点是仍然可以从内部实例化它们。幸运的是,尽管您无法使用私有构造函数扩展类。


9

这是一种方法:

class SomeClass {
    private static myStaticVariable = "whatever";
    private static __static_ctor = (() => { /* do static constructor stuff :) */ })();
}

__static_ctor这是一个立即调用的函数表达式。Typescript将输出代码以在生成的类的末尾调用它。

更新:对于静态构造函数中不再允许静态成员引用的泛型类型,您现在需要一个额外的步骤:

class SomeClass<T> {
    static myStaticVariable = "whatever";
    private ___static_ctor = (() => { var someClass:SomeClass<T> ; /* do static constructor stuff :) */ })();
    private static __static_ctor = SomeClass.prototype.___static_ctor();
}

当然,无论如何,您都可以在类之后调用通用类型的静态构造函数,例如:

class SomeClass<T> {
    static myStaticVariable = "whatever";
    private __static_ctor = (() => { var example: SomeClass<T>; /* do static constructor stuff :) */ })();
}
SomeClass.prototype.__static_ctor();

只要记住不要this__static_ctor上面(显然)使用。


这种方式仍然为该类发出一个构造函数。
callmemorty

1
“这种方式”是一种hack,不会按预期改变编译器的正常操作。甚至class SomeClass {}生成一个构造函数-几乎没有什么值得评论的,就好像引入了新问题一样。;)仅供参考:JS中没有真正的“构造函数”,只有在对象或通过调用时具有“ this”的函数new。无论对于任何“阶级”,这都是存在的。
詹姆斯·威尔金斯

6

今天(31/07/2018),我得到了相同的用例,发现这是一种解决方法。它基于我的研究,对我有用。 期望 -在TypeScript中实现以下目标:

var myStaticClass = {
    property: 10,
    method: function(){} 
}

我这样做:

//MyStaticMembers.ts
namespace MyStaticMembers {
        class MyStaticClass {
           static property: number = 10;
           static myMethod() {...}
        }
        export function Property(): number {
           return MyStaticClass.property;
        }
        export function Method(): void {
           return MyStaticClass.myMethod();
        }
     }

因此,我们将按以下方式使用它:

//app.ts
/// <reference path="MyStaticMembers.ts" />
    console.log(MyStaticMembers.Property);
    MyStaticMembers.Method();

这对我有用。如果有人有其他更好的建议,请让我们所有人听听!谢谢...


3

之所以存在诸如C#之类的静态类,是因为没有其他顶级结构可以对数据和函数进行分组。但是,在JavaScript中,它们确实如此,因此像您一样声明一个对象更加自然。要更紧密地模仿类语法,可以声明如下方法:

const myStaticClass = {
    property: 10,

    method() {

    }
}

1
这种方法不会混淆您的工作流程吗?一方面,您使用类和实例,然后突然又开始在常规的旧JS对象中声明数据...使用静态类还有助于提高可读性,这不仅涉及语言的内部工作原理。
Kokodoko

4
我发现它不会干扰我的工作流程;相反,我发现在模块中使用无类JavaScrpit对象或仅使用普通函数和常量非常方便。但是,我也尽量避免具有全局状态,因此很少需要像静态类变量这样的东西。
Yogu

1

参见http://www.basarat.com/2013/04/typescript-static-constructors-for.html

这是一种“伪造”静态构造函数的方法。这并非没有危险-请参见所引用的codeplex项

class Test {
    static foo = "orig";

    // Non void static function
    static stat() {
        console.log("Do any static construction here");
        foo = "static initialized";
        // Required to make function non void
        return null;
    }
    // Static variable assignment
    static statrun = Test.stat();
}

// Static construction will have been done:
console.log(Test.foo);

1

实现此目的的一种可能方法是在另一个类中包含一个类的静态实例。例如:

class SystemParams
{
  pageWidth:  number = 8270;
  pageHeight: number = 11690;  
}

class DocLevelParams
{
  totalPages: number = 0;
}

class Wrapper
{ 
  static System: SystemParams = new SystemParams();
  static DocLevel: DocLevelParams = new DocLevelParams();
}

然后,可以使用包装器访问参数,而不必声明其实例。例如:

Wrapper.System.pageWidth = 1234;
Wrapper.DocLevel.totalPages = 10;

因此,您可以获得JavaScript类型对象的好处(如原始问题中所述),但具有能够添加TypeScript类型的好处。此外,它避免了必须在类中所有参数之前添加“ static”。


1

使用ES6外部模块,可以像这样实现:

// privately scoped array
let arr = [];

export let ArrayModule = {
    add: x => arr.push(x),
    print: () => console.log(arr),
}

这防止了内部模块和名称空间的使用,这被TSLint [1] [2]认为是不好的做法,允许私有和公共作用域,并防止不必要的类对象的初始化。


0

我正在寻找类似的东西,然后遇到了一个叫做 Singleton Pattern

参考:单例模式

我正在研究BulkLoader类,以加载不同类型的文件,并希望为其使用Singleton模式。这样,我可以从主应用程序类加载文件,并轻松地从其他类检索加载的文件。

以下是一个简单示例,说明如何使用TypeScript和Singleton模式为游戏制作得分管理器。

class SingletonClass {

private static _instance:SingletonClass = new SingletonClass();

private _score:number = 0;

constructor() {
    if(SingletonClass._instance){
        throw new Error("Error: Instantiation failed: Use SingletonDemo.getInstance() instead of new.");
    }
    SingletonClass._instance = this;
}

public static getInstance():SingletonClass
{
    return SingletonClass._instance;
}

public setScore(value:number):void
{
    this._score = value;
}

public getScore():number
{
    return this._score;
}

public addPoints(value:number):void
{
    this._score += value;
}

public removePoints(value:number):void
{
    this._score -= value;
}   }

然后,在您其他班级的任何地方,您都可以通过以下方式访问Singleton:

var scoreManager = SingletonClass.getInstance();
scoreManager.setScore(10); scoreManager.addPoints(1);
scoreManager.removePoints(2); console.log( scoreManager.getScore() );

0

您还可以使用关键字namespace来组织变量,类,方法等。见文件

namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }

    const lettersRegexp = /^[A-Za-z]+$/;
    const numberRegexp = /^[0-9]+$/;

    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }

    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}

请参阅弗洛里安在上面的评论“已弃用。tslint也将不再允许您对模块和名称空间执行此操作。请阅读此处:palantir.github.io/tslint/rules/no-namespace”
reggaeguitar,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.