public static const în TypeScript


Există așa ceva ca și constante publice statice în TypeScript? Am o clasă care arată astfel:

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

În acea clasă, pot face Library.BOOK_SHELF_NONE și tsc nu se plânge. Dar dacă încerc să folosesc clasa Library în altă parte și încerc să fac același lucru, nu o recunoaște.

Răspuns #1

Dacă chiar ați fi dorit ceva care să se comporte mai mult ca o valoare constantă statică în browserele moderne (în sensul că nu poate fi schimbată de alt cod), ați putea adăuga un accesator doar cu get în clasa Library (acest lucru va funcționa doar pentru browserele ES5+ și 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);

Dacă îl rulezi, vei vedea cum încercarea de a seta proprietatea BOOK_SHELF_NONE la o valoare nouă nu funcționează.

2.0

În TypeScript 2.0, poți folosi readonly pentru a obține rezultate foarte similare:

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

Sintaxa este puțin mai simplă și mai evidentă. Cu toate acestea, compilatorul împiedică modificările mai degrabă decât timpul de rulare (spre deosebire de primul exemplu, unde modificarea nu ar fi fost permisă deloc, așa cum s-a demonstrat).

Răspunsul #2

Poți să faci asta folosind spații de nume, astfel:

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

Apoi îl poți importa din orice altă parte:

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

Dacă ai nevoie și de o clasă acolo, includ-o în interiorul namespace-ului: export class Book {...}

Răspunsul #3

Iată în ce este compilat acest fragment TS (prin 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;
});

După cum vedeți, ambele proprietăți definite ca public static sunt pur și simplu atașate funcției exportate (ca proprietățile ei); prin urmare, ele ar trebui să fie accesibile atâta timp cât accesați corect funcția însăși.

Răspunsul #4

Între timp, acest lucru poate fi rezolvat printr-un decorator în combinație cu Object.freeze sau Object.defineProperty, eu folosesc asta, e puțin mai frumos decât să folosești o mulțime de getters. Poți copia/lipii direct în TS Playground pentru a vedea cum funcționează. - Există două opțiuni

Fă câmpurile individuale „final”

Decoratorul următor convertește atât câmpurile statice, cât și cele non-statice anotate în „proprietăți doar-cu-getter”.

Notă: Dacă o variabilă de instanță fără valoare inițială este notată cu @final, atunci prima valoare atribuită (indiferent când) va fi cea 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.

Decoratorul: Asigură-te că incluzi asta în codul tău!

/**
* 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
        });
    }
}

Ca alternativă la decoratorul de mai sus, ar exista și o versiune strictă a acestuia, care ar arunca chiar și o Eroare atunci când cineva ar încerca să atribuie o valoare câmpului cu "use strict"; activat. (Totuși, aceasta este doar partea statică)

/**
 * 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
    });
}

Fă ca fiecare câmp static să fie "final"

Dezavantaj posibil: Acest lucru va funcționa doar pentru TOATE elementele statice ale acelei clase sau pentru niciuna, dar nu poate fi aplicat elementelor statice specifice.

/**
* 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."