TypeScript 静态类

Posted

技术标签:

【中文标题】TypeScript 静态类【英文标题】:TypeScript static classes 【发布时间】:2012-10-24 03:11:05 【问题描述】:

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

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

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

在 TypeScript 中,我宁愿采用我的 C-sharpy 方法,但似乎 TS 中不存在静态类。 这个问题的合适解决方案是什么?

【问题讨论】:

我想指出,即使使用像您的 JS 示例中那样的文字对象,也是类型安全的并且可以实现您想要的。 【参考方案1】:

自 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

【讨论】:

在处理静态类时,这是最好的回应,我对此表示赞同。 Singleton 用于同一类实例的共享内存模式。此外,静态类根据定义没有实例,因此如果客户端尝试对其进行初始化,则必须抛出异常。 我们可以做一个抽象类吗? @KimchiMan - 是的,TypeScript 支持 abstract 关键字。 更新为使用abstract! @KimchiMan - 好主意! 这是不是比将构造函数标记为私有更好的方法?【参考方案2】:

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

在 C# 中,静态类只是一个不能被子类化并且必须只包含静态方法的类。 C# 不允许在类之外定义函数。然而,在 TypeScript 中这是可能的。

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

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

这样你可以在外部访问m.f(),但不能访问s,并且不能扩展模块。

有关更多详细信息,请参阅 TypeScript specification。

【讨论】:

所以一个模块可以有一个静态方法,但一个类不能?但模块不能有静态数据。它不像 JS 那样方便地包装数据和代码而无需实例化任何东西。 html 中包含.js 可能会很有用。因此,对于Angular 2,您可能正在使用System...所以在bootstrap import 之前使用System.import("Folder/M");(或编译后的.js 文件的路径) 已弃用。 tslint 也不会再让你为模块和命名空间这样做了。在这里阅读:palantir.github.io/tslint/rules/no-namespace @florian leitgeb 那么首选方法是什么,只有静态方法和/或抽象关键字的类?与现在似乎已弃用的模块相比,这似乎很糟糕 有可能,我建议看看***.com/a/13222267/5724101【参考方案3】:

在Typescript Language Specification 的 8.2.1 中描述了定义类的静态属性和方法:

class Point  
  constructor(public x: number, public y: number)  
    throw new Error('cannot instantiate using a static class');
   
  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() 是一个静态(或“类”)方法。

【讨论】:

这显示了如何创建一个静态方法,它没有回答关于静态的问题(除非真正的问题实际上是关于静态方法)。 感谢您的评论。它描述了如何创建静态属性和方法,它们结合在一起允许创建一个具有数据和功能的类,而无需实例化。虽然不是专门的“静态类”,但它确实满足了 OP 的 javascript 示例所描述的要求。 c# 在版本 2 之前没有静态类。它们存在于 c# 中只是为了防止您实例化它们。你不能用 javascript 做到这一点,所以它没有多大意义 @Simon_Weaver 你不能用javascript做什么?防止类被实例化?无论如何,Typescript 并不关心运行时,只要您在尝试执行不允许执行的操作时遇到编译错误,这就是我们所需要的。 我想我的意思是'我们在类级别没有静态关键字(这意味着你不能创建类的实例)'直到版本 2。但再次阅读它我认为我的评论无论如何都没有抓住重点。 OP 并没有真正为整个班级寻找“静态关键字”【参考方案4】:

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

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

这个 sn-p 将允许您使用类似于 C# 对应物的“静态”类,唯一的缺点是仍然可以从内部实例化它们。幸运的是,虽然您不能使用私有构造函数扩展类。

【讨论】:

【参考方案5】:

我今天(2018 年 7 月 31 日)遇到了同样的用例,发现这是一种解决方法。它基于我的研究并且对我有用。 期望 - 在 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();

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

【讨论】:

【参考方案6】:

这是一种方式:

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

请记住不要在上面的__static_ctor 中使用this(显然)。

【讨论】:

这种方式仍然会为类发出一个构造函数。 "This way" 是一种 hack,不会像预期的那样改变编译器的正常操作。甚至class SomeClass 也会生成一个构造函数——几乎不值得评论,好像引入了一个新问题一样。 ;) 仅供参考:JS 中没有真正的“构造函数”——只有在对象上或通过new 调用时具有“this”的函数。这对于任何“类”来说都是存在的。【参考方案7】:

存在 C# 等语言中的静态类,因为没有其他***构造可以对数据和函数进行分组。然而,在 JavaScript 中,它们确实如此,因此像您所做的那样只声明一个对象要自然得多。为了更接近地模仿类语法,您可以像这样声明方法:

const myStaticClass = 
    property: 10,

    method() 

    

【讨论】:

这种方法不会混淆您的工作流程吗?一方面,您使用类和实例,然后突然又开始在常规的旧 JS 对象中声明数据……使用静态类也有助于提高可读性,这不仅仅是语言的内部工作原理。 我不觉得它会影响我的工作流程;相反,我发现在模块中使用无类 JavaScrpit 对象或仅使用普通函数和常量非常方便。不过,我也尽量避免使用全局状态,因此我很少需要静态类变量之类的东西。【参考方案8】:

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

// privately scoped array
let arr = [];

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

这可以防止使用被 TSLint [1] [2] 认为是不好的做法的内部模块和命名空间,允许私有和公共范围,并防止不需要的类对象的初始化。

【讨论】:

【参考方案9】:

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

这是一种“伪造”静态构造函数的方法。这并非没有危险 - 请参阅referenced codeplex item。

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

【讨论】:

【参考方案10】:

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

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 访问参数,而无需声明它的实例。例如:

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

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

【讨论】:

【参考方案11】:

您可以将抽象类public static readonly members一起使用,以实现与您正在寻找的非常相似的东西。我相信您正在寻找类似 C# 或 C/C++ 的 struct 之类的东西来将小块数据组织在一起。

抽象类最酷的地方在于

它们不能被实例化, 它们只能派生自和 它们可以为其中定义的部分或全部方法提供基本实现。

您甚至可以使用这种技术在某种程度上模仿enum(例如,您不能打开它们),但具有的属性可以不仅仅是字符串或数字。

// you can omit the public keyword because it's the default in TS, I left it here for clarity

export abstract class RequestTypes 
  public static readonly All = 'All types';
  public static readonly Partners = 'Partners';
  public static readonly Articles = 'Articles';

【讨论】:

可以在 JavaScript/TypeScript 中打开任何值(或表达式)。 查看我的答案:***.com/a/69312788/2339176【参考方案12】:

我正在寻找类似的东西,偶然发现了一个叫做Singleton Pattern的东西。

参考:Singleton Pattern

我正在开发一个 BulkLoader 类来加载不同类型的文件和 想为它使用单例模式。这样我可以加载文件 从我的主应用程序类中轻松检索加载的文件 来自其他类。

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

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

然后,您可以在其他班级的任何地方访问 单身人士:

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

【讨论】:

【参考方案13】:

您也可以使用关键字namespace 来组织您的变量、类、方法等。见doc

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

【讨论】:

查看 Florian 上面的评论“这已被弃用。而且 tslint 不会让你再为模块和命名空间这样做。阅读此处:palantir.github.io/tslint/rules/no-namespace” 【参考方案14】:

您可以在 Typescript 中创建一个类,如下所示:

export class Coordinate 
        static x: number;
        static y: number;
        static gradient() 
            return y/x;
        
    

并在“没有”实例化的情况下引用它的属性和方法:

Coordinate.x = 10;
Coordinate.y = 10;
console.log(`x of $Coordinate.x and y of $Coordinate.y has gradient of $Coordinate.gradient()`);

Fyi 使用反引号 `` 和插值语法 $ 可以轻松地将代码与文本混合 :-)

【讨论】:

或者你可以导出一个abstract class,这在 TypeScript 中是可行的。添加static readonly 成员,您基本上就拥有了相当于静态C# 类的功能。【参考方案15】:

我的首选方法是只使用一个 const 对象(主要是代替枚举):

const RequestTypes2 = 
    All: 'All types',
    Partners: 'Partners',
    Articles: 'Articles',
 as const; // need the "const" to force the property types to be string literal types (hover RequestTypes2 to see!)

// now you can do this (hover AllowedRequestTypes to see the inferred type)
type AllowedRequestTypes = typeof RequestTypes2[keyof typeof RequestTypes2];

function doRequest(requestType: AllowedRequestTypes) 



// these should work 
doRequest('Partners');
doRequest(RequestTypes2.All);
doRequest(RequestTypes.Articles);   // the property's type is "Articles" (string literal type)

// this fails
doRequest('Incorrect');

检查this TS playground。

【讨论】:

以上是关于TypeScript 静态类的主要内容,如果未能解决你的问题,请参考以下文章

Typescript:使用静态内部类的类型

TypeScript:继承类中静态方法的自引用返回类型

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

带有 typescript 和 mongoose 的静态方法会:“一个接口只能扩展一个类或另一个接口。”

TypeScript核心篇——类(class)-可选参数-存取器-构造函数-静态属性方法-抽象类

TypeScript 速成教程