【问题标题】:public static const in TypeScriptTypeScript 中的公共静态常量
【发布时间】:2014-05-24 09:24:05
【问题描述】:

TypeScript 中是否存在公共静态常量之类的东西?我有一个看起来像这样的类:

export class Library {
  public static BOOK_SHELF_NONE: string = "None";
  public static BOOK_SHELF_FULL: string = "Full";
}

在那堂课上,我可以做Library.BOOK_SHELF_NONE 并且 tsc 不会抱怨。但是如果我尝试在其他地方使用类库,并尝试做同样的事情,它就无法识别它。

【问题讨论】:

  • 你能举一个在别处使用该类的例子吗?
  • @raina77ow 当我开始添加我如何使用它的代码时,我意识到这是因为 tsc 抱怨的命名空间。如果您想将其作为答案,我可以将其标记为完整,因为您帮助我找到了它。我真的不喜欢回答自己的问题。
  • 啊,原来是这个原因。 )很高兴我帮助了;我将添加显示编译结果的答案。
  • 我还要添加 readonly 以确保如果您尝试重新分配这些字段,TypeScript 会警告您:public static readonly BOOK_SHELF_FULL

标签: typescript


【解决方案1】:

如果您确实想要在现代浏览器中表现得更像静态常量值的东西(因为它不能被其他代码更改),您可以添加一个 get only 访问器到 Library 类(这个仅适用于 ES5+ 浏览器和 NodeJS):

export class Library {
    public static get BOOK_SHELF_NONE():string { return "None"; }
    public static get BOOK_SHELF_FULL():string { return "Full"; }   
}

var x = Library.BOOK_SHELF_NONE;
console.log(x);
Library.BOOK_SHELF_NONE = "Not Full";
x = Library.BOOK_SHELF_NONE;
console.log(x);

如果您运行它,您将看到尝试将BOOK_SHELF_NONE 属性设置为新值是如何不起作用的。

2.0

在 TypeScript 2.0 中,您可以使用readonly 来实现非常相似的结果:

export class Library {
    public static readonly BOOK_SHELF_NONE = "None";
    public static readonly BOOK_SHELF_FULL = "Full";
}

语法更简单,更明显。但是,编译器会阻止更改而不是运行时(与第一个示例不同,在第一个示例中,根本不允许更改)。

【讨论】:

  • 这个解决方案有一个缺点 - 你不能使用这些 dynamic 值作为类型约束,例如:type MyType: Library.BOOK_SHELF_NONE | Library.BOOK_SHELF_FULL;
  • 假设我有多个常量类,我希望我的所有常量类都包含一个特定的方法来检查类中是否定义了任意常量。我试过:class Constants { isDefined(id) {if ... return boolean;}}class UiConstants extends Constants { public static get ARTICLE():string { return 'ARTICLE'; } }。如果我使用 UiConstants 而没有定义新的 UiConstants.ARTICLEUiConstants.isDefined() 仍未定义。如果我实例化 new UiConstants 然后 UiConstants.isDefined() 方法被定义但 UiConstants.ARTICLE 消失。
  • @AdrianMoisa - 请提出一个新问题。
  • @hex-请提出一个新问题。
  • 枚举呢?
【解决方案2】:

您可以使用namespaces 进行操作,如下所示:

export namespace Library {
    export const BOOK_SHELF_NONE: string = 'NONE';
}

然后你可以从其他任何地方导入它:

import {Library} from './Library';
console.log(Library.BOOK_SHELF_NONE);

如果你需要一个类,也可以将它包含在命名空间中:export class Book {...}

【讨论】:

  • 如果我在对象的初始化中使用常量,它们似乎无法被识别。例如:{ type: Library.BOOK_SHELF_NONE } 似乎认为命名空间没有名为 BOOK_SHELF_NONE 的导出。但是,如果我只是使用引用设置一个局部变量,它就可以很好地解决它。 TS 2.2
【解决方案3】:

这是这个 TS sn-p 编译成的内容(通过 TS Playground):

define(["require", "exports"], function(require, exports) {
    var Library = (function () {
        function Library() {
        }
        Library.BOOK_SHELF_NONE = "None";
        Library.BOOK_SHELF_FULL = "Full";
        return Library;
    })();
    exports.Library = Library;
});

如您所见,定义为public static 的两个属性都简单地附加到导出的函数(作为其属性);因此,只要您正确访问函数本身,它们就应该可以访问。

【讨论】:

    【解决方案4】:

    同时这可以通过装饰器结合Object.freezeObject.defineProperty 来解决,我正在使用它,它比使用大量吸气剂要漂亮一点。您可以直接复制/粘贴 TS Playground 以查看它的实际效果。 - 有两种选择


    将各个字段设为“最终”

    以下装饰器将带注释的静态和非静态字段都转换为“getter-only-properties”。

    注意:如果一个没有初始值的实例变量被注释为@final,那么第一个赋值(无论何时)都是最后一个。

    // example
    class MyClass {
        @final
        public finalProp: string = "You shall not change me!";
    
        @final
        public static FINAL_FIELD: number = 75;
    
        public static NON_FINAL: string = "I am not final."
    }
    
    var myInstance: MyClass = new MyClass();
    myInstance.finalProp = "Was I changed?";
    MyClass.FINAL_FIELD = 123;
    MyClass.NON_FINAL = "I was changed.";
    
    console.log(myInstance.finalProp);  // => You shall not change me!
    console.log(MyClass.FINAL_FIELD);   // => 75
    console.log(MyClass.NON_FINAL);     // => I was changed.
    

    装饰器:确保将其包含在代码中!

    /**
    * Turns static and non-static fields into getter-only, and therefor renders them "final".
    * To use simply annotate the static or non-static field with: @final
    */
    function final(target: any, propertyKey: string) {
        const value: any = target[propertyKey];
        // if it currently has no value, then wait for the first setter-call
        // usually the case with non-static fields
        if (!value) {
            Object.defineProperty(target, propertyKey, {
                set: function (value: any) {
                    Object.defineProperty(this, propertyKey, {
                        get: function () {
                            return value;
                        },
                        enumerable: true,
                        configurable: false
                    });
                },
                enumerable: true,
                configurable: true
            });
        } else { // else, set it immediatly
            Object.defineProperty(target, propertyKey, {
                get: function () {
                    return value;
                },
                enumerable: true
            });
        }
    }
    

    作为上述装饰器的替代方案,还有一个严格的版本,当有人试图为设置了"use strict"; 的字段分配一些值时,它甚至会抛出错误。 (不过这只是静态部分)

    /**
     * Turns static fields into getter-only, and therefor renders them "final".
     * Also throws an error in strict mode if the value is tried to be touched.
     * To use simply annotate the static field with: @strictFinal
     */
    function strictFinal(target: any, propertyKey: string) {
        Object.defineProperty(target, propertyKey, {
            value: target[propertyKey],
            writable: false,
            enumerable: true
        });
    }
    

    使每个静态字段“最终”

    可能的缺点:这仅适用于该类的所有静态或不适用,但不能应用于特定的静态。

    /**
    * Freezes the annotated class, making every static 'final'.
    * Usage:
    * @StaticsFinal
    * class MyClass {
    *      public static SOME_STATIC: string = "SOME_STATIC";
    *      //...
    * }
    */
    function StaticsFinal(target: any) {
        Object.freeze(target);
    }
    
    // Usage here
    @StaticsFinal
    class FreezeMe {
        public static FROZEN_STATIC: string = "I am frozen";
    }
    
    class EditMyStuff {
        public static NON_FROZEN_STATIC: string = "I am frozen";
    }
    
    // Test here
    FreezeMe.FROZEN_STATIC = "I am not frozen.";
    EditMyStuff.NON_FROZEN_STATIC = "I am not frozen.";
    
    console.log(FreezeMe.FROZEN_STATIC); // => "I am frozen."
    console.log(EditMyStuff.NON_FROZEN_STATIC); // => "I am not frozen."
    

    【讨论】:

    • 我认为这是最干净的方法。谢谢!
    • 一个小补充:if (!value) 应该是 if (value === undefined) 让静态值是 ""0
    【解决方案5】:

    感谢 WiredPrairie!

    只是为了扩展您的答案,这里是定义常量类的完整示例。

    // CYConstants.ts
    
    class CYConstants {
        public static get NOT_FOUND(): number    { return -1; }
        public static get EMPTY_STRING(): string { return ""; }
    }
    
    export = CYConstants;
    

    使用

    // main.ts
    
    import CYConstants = require("./CYConstants");
    
    console.log(CYConstants.NOT_FOUND);    // Prints -1
    console.log(CYConstants.EMPTY_STRING); // Prints "" (Nothing!)
    

    【讨论】:

      【解决方案6】:

      以下解决方案也适用于 TS 1.7.5。

      // Constancts.ts    
      export const kNotFoundInArray = -1;
      export const AppConnectionError = new Error("The application was unable to connect!");
      export const ReallySafeExtensions = ["exe", "virus", "1337h4x"];
      

      使用方法:

      // Main.ts    
      import {ReallySafeExtensions, kNotFoundInArray} from "./Constants";
      
      if (ReallySafeExtensions.indexOf("png") === kNotFoundInArray) {
          console.log("PNG's are really unsafe!!!");
      }
      

      【讨论】:

      • 这真的很酷,但是如果您使用它来定义多个值(就像您可能在其他语言的 Enum 中一样),那么您将无法单独导入所有值。这很快就会变得很麻烦!
      • 100% 同意。可能能够命名它们,但我还没有调查过。也许您可以从“./Constants.ts”导入 {ArrayConstants},然后执行 ArrayConstants.NotFound 之类的操作。当我有时间时,必须多玩它。
      • 是的,您可以使用命名空间来避免单独使用值,请在此处阅读我自己的答案。
      【解决方案7】:

      只需简单地在你的类中“导出”变量和“导入”

      export var GOOGLE_API_URL = 'https://www.googleapis.com/admin/directory/v1';
      
      // default err string message
      export var errStringMsg = 'Something went wrong';
      

      现在使用它,

      import appConstants = require('../core/AppSettings');
      console.log(appConstants.errStringMsg);
      console.log(appConstants.GOOGLE_API_URL);
      

      【讨论】:

        【解决方案8】:

        您可以使用 getter,这样您的属性将是只读的。 示例:

        export class MyClass {
            private _LEVELS = {
                level1: "level1",
                level2: "level2",
                level2: "level2"
            };
        
            public get STATUSES() {
                return this._LEVELS;
            }
        }
        

        在另一个类中使用:

        import { MyClass } from "myclasspath";
        class AnotherClass {
            private myClass = new MyClass();
        
            tryLevel() {
               console.log(this.myClass.STATUSES.level1);
            }
        }
        

        【讨论】:

        • 这使得预期的常量可写:let a = new MyClass(); a.STATUSES.level1 = 'not level1'; let plainWrong = new MyClass().STATUSES.level1; // 'not level1'
        • 嗯...它不是最终常量,即使成员是私有的,您也可以完全访问对象_LEVELS。我刚试过:this.myClass.STATUSES.level1 = 'blabla';它奏效了!您将指针本身设为私有并将其克隆为指向相同的 getter,使其成为公共
        猜你喜欢
        • 2016-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-19
        • 2015-03-19
        • 2018-05-20
        • 2011-09-23
        相关资源
        最近更新 更多