【问题标题】:TypeScript static classesTypeScript 静态类
【发布时间】: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 示例中那样的文字对象,也是类型安全的并且可以实现您想要的。

标签: javascript class static typescript


【解决方案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 那么首选方法是什么,只有静态方法和/或抽象关键字的类?与现在似乎已弃用的模块相比,这似乎很糟糕
  • 有可能,我建议看看stackoverflow.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';
            }
            

            【讨论】:

            【解决方案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。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-12-03
                • 1970-01-01
                • 2019-08-15
                • 1970-01-01
                • 2023-04-07
                • 1970-01-01
                相关资源
                最近更新 更多