const statica pubblica in TypeScript


186

Esistono costanti statiche pubbliche in TypeScript? Ho una lezione che assomiglia a:

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

In quella classe, posso farlo Library.BOOK_SHELF_NONEe il tsc non si lamenta. Ma se provo ad usare la classe Library altrove e provo a fare la stessa cosa, non la riconosce.


1
Puoi mostrare un esempio dell'uso di quella classe altrove?
raina77ow,

@ raina77ow Quando ho iniziato ad aggiungere il codice per come lo uso, mi sono reso conto che è a causa dello spazio dei nomi che il tsc si è lamentato. Se vuoi inserirla come risposta, posso contrassegnarla come completa poiché mi hai aiutato a trovarla. Non mi piace molto rispondere alle mie domande.
Crystal,

Ah, quella era la causa. ) Sono contento di aver aiutato; Aggiungerò la risposta che mostra i risultati della compilazione.
raina77ow,

1
Aggiungerei anche readonlyper assicurarmi che TypeScript ti avvertirà se hai mai provato a riassegnare quei campi:public static readonly BOOK_SHELF_FULL
Jan Molak,

Risposte:


26

Ecco in cosa è stato compilato questo snippet TS (tramite 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;
});

Come vedi, entrambe le proprietà definite come public staticsono semplicemente associate alla funzione esportata (come le sue proprietà); pertanto dovrebbero essere accessibili purché si acceda correttamente alla funzione stessa.


430

Se volevi qualcosa che si comportava più come un valore di costante statica nei browser moderni (in quanto non può essere modificato da un altro codice), potresti aggiungere un getunico accessorio alla Libraryclasse (funzionerà solo per i browser ES5 + e 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);

Se lo esegui, vedrai come il tentativo di impostare la BOOK_SHELF_NONEproprietà su un nuovo valore non funziona.

2.0

In TypeScript 2.0, è possibile utilizzare readonlyper ottenere risultati molto simili:

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

La sintassi è un po 'più semplice e più ovvia. Tuttavia, il compilatore impedisce le modifiche anziché il tempo di esecuzione (diversamente dal primo esempio, in cui la modifica non sarebbe affatto consentita, come dimostrato).


26
questa è una buona risposta il mio codice ora è più felice per questo.
gonzofish,

7
Questa soluzione presenta tuttavia uno svantaggio: non è possibile utilizzare questi valori dinamici come vincolo di tipo, ad esempio:type MyType: Library.BOOK_SHELF_NONE | Library.BOOK_SHELF_FULL;
Kostiantyn Kolesnichenko

2
@AdrianMoisa - per favore, fai una nuova domanda.
WiredPrairie,

2
@ hex-per favore, fai una nuova domanda.
WiredPrairie,

1
E gli enum?
curious95

40

Puoi farlo usando gli spazi dei nomi , in questo modo:

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

Quindi puoi importarlo da qualsiasi altra parte:

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

Se hai bisogno di una classe, includila anche nello spazio dei nomi: export class Book {...}


2
Se sto usando le costanti nell'inizializzazione di un oggetto, non sembrano essere riconosciute. Ad esempio: { type: Library.BOOK_SHELF_NONE }sembra pensare che lo spazio dei nomi non abbia alcuna esportazione chiamata BOOK_SHELF_NONE. Tuttavia, se ho appena impostato una variabile locale con il riferimento, la risolve bene. TS 2.2
schiaccia il

13

Nel frattempo questo può essere risolto attraverso un decoratore in combinazione con Object.freezeo Object.defineProperty, sto usando questo, è un po 'più bello rispetto all'utilizzo di tonnellate di getter. Puoi copiare / incollare questo TS Playground direttamente per vederlo in azione. - Ci sono due opzioni


Rendi "finali" i singoli campi

Il seguente decoratore converte i campi statici e non statici con annotazioni in "proprietà solo getter".

Nota : se viene annotata una variabile di istanza senza valore iniziale @final, il primo valore assegnato (indipendentemente da quando) sarà quello finale.

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

The Decorator: assicurati di includerlo nel tuo codice!

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

In alternativa al decoratore sopra, ci sarebbe anche una versione rigorosa di questo, che potrebbe anche generare un errore quando qualcuno ha cercato di assegnare un valore al campo con "use strict";l'impostazione. (Questa è solo la parte statica però)

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

Rendi "finale" ogni campo statico

Possibile inconveniente: funzionerà solo per TUTTE le statiche di quella classe o per nessuna, ma non può essere applicato a statiche specifiche.

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

Penso che questo sia l'approccio più pulito. Grazie!
Thibs,

Una piccola aggiunta: if (!value)dovrebbe essere quella if (value === undefined)di lasciare che i valori statici siano ""o 0.
Leon Adler,

6

Grazie WiredPrairie!

Solo per espandere un po 'la tua risposta, ecco un esempio completo di definizione di una classe di costanti.

// CYConstants.ts

class CYConstants {
    public static get NOT_FOUND(): number    { return -1; }
    public static get EMPTY_STRING(): string { return ""; }
}

export = CYConstants;

Usare

// main.ts

import CYConstants = require("./CYConstants");

console.log(CYConstants.NOT_FOUND);    // Prints -1
console.log(CYConstants.EMPTY_STRING); // Prints "" (Nothing!)

2

La seguente soluzione funziona anche da 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"];

Usare:

// Main.ts    
import {ReallySafeExtensions, kNotFoundInArray} from "./Constants";

if (ReallySafeExtensions.indexOf("png") === kNotFoundInArray) {
    console.log("PNG's are really unsafe!!!");
}

Questo è davvero bello, ma se lo stai usando per definire più di un paio di valori (come potresti fare in un Enum in altre lingue), allora sei bloccato a importare tutti i valori singolarmente. Questo potrebbe diventare ingombrante molto velocemente!
Corey Larson,

100% concordato. Potrebbe essere in grado di spaziarli ma non l'ho esaminato. Forse potresti importare {ArrayConstants} da "./Constants.ts" e quindi fare qualcosa come ArrayConstants.NotFound. Devo giocarci di più quando ho tempo.
Andrew,

Sì, puoi usare gli spazi dei nomi per evitare di usare i valori individualmente, leggi la mia risposta qui.
Ivan Castellanos,

0

Basta semplicemente 'esportare' la variabile e 'importare' nella tua classe

export var GOOGLE_API_URL = 'https://www.googleapis.com/admin/directory/v1';

// default err string message
export var errStringMsg = 'Something went wrong';

Ora usalo come,

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

0

Puoi usare un getter, in modo che la tua proprietà sia di sola lettura. Esempio:

export class MyClass {
    private _LEVELS = {
        level1: "level1",
        level2: "level2",
        level2: "level2"
    };

    public get STATUSES() {
        return this._LEVELS;
    }
}

Utilizzato in un'altra classe:

import { MyClass } from "myclasspath";
class AnotherClass {
    private myClass = new MyClass();

    tryLevel() {
       console.log(this.myClass.STATUSES.level1);
    }
}

Questo rende ciò che ci si aspetta sia sempre scrivibile:let a = new MyClass(); a.STATUSES.level1 = 'not level1'; let plainWrong = new MyClass().STATUSES.level1; // 'not level1'
Leon Adler,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.