TypeScript の public static const
TypeScriptにおいて、public static定数のようなものは存在しますか? 私は次のようなクラスを持っています:
export class Library {
public static BOOK_SHELF_NONE: string = "None";
public static BOOK_SHELF_FULL: string = "Full";
}
そのクラスでは、Library.BOOK_SHELF_NONE を実行できますし、tsc は文句を言いません。しかし、他の場所で Library クラスを使おうとして同じことをしようとすると、認識されません。
回答 #1
もし最新のブラウザでより静的な定数値のように振る舞うもの(他のコードによって変更できないもの)が欲しい場合、Library クラスに get のみのアクセサを追加することができます(これは 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";
}
構文は少し簡単でより明白です。しかし、コンパイラーはランタイムではなく変更を防ぎます(最初の例とは異なり、その例では変更は全く許可されないことが示されました)。
回答 #2
このように名前空間を使って行うことができます:
export namespace Library {
export const BOOK_SHELF_NONE: string = 'NONE';
}
それから、どこからでもそれをインポートできます:
import {Library} from './Library';
console.log(Library.BOOK_SHELF_NONE);
もしそこにクラスも必要であれば、名前空間内に含めてください: export class Book {...}
回答 #3
これは TS スニペットをコンパイルした結果です(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.freeze または Object.defineProperty の組み合わせで解決できます。私はこれを使っていますが、たくさんのゲッターを使うより少し見た目がきれいです。これを直接 TS Playground にコピー/ペーストして動作を確認できます。- 二つのオプションがあります
個々のフィールドを「final」にする
次のデコレーターは、注釈付きの静的および非静的フィールドの両方を「ゲッター専用プロパティ」に変換します。
注意: 初期値のないインスタンス変数が@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
});
}
すべての静的フィールドを「final」にする
考えられる欠点: これはそのクラスのすべての静的メンバーに対してのみ機能し、一部の静的メンバーに適用することはできません。
/**
* 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."
