öffentliche statische Konstante in TypeScript


185

Gibt es in TypeScript öffentliche statische Konstanten? Ich habe eine Klasse, die aussieht wie:

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

In dieser Klasse kann ich Library.BOOK_SHELF_NONEund der tsc beschwert sich nicht. Aber wenn ich versuche, die Klassenbibliothek an anderer Stelle zu verwenden und das Gleiche zu tun, wird sie nicht erkannt.


1
Können Sie ein Beispiel für die Verwendung dieser Klasse an anderer Stelle zeigen?
raina77ow

@ raina77ow Als ich anfing, den Code für meine Verwendung hinzuzufügen, stellte ich fest, dass sich der tsc wegen des Namespaces beschwerte. Wenn Sie das als Antwort eingeben möchten, kann ich es als vollständig markieren, da Sie mir geholfen haben, es zu finden. Ich mag es nicht wirklich, meine eigenen Fragen zu beantworten.
Kristall

Ah, das war die Ursache. ) Ich bin froh, dass ich geholfen habe. Ich werde die Antwort hinzufügen, die die Ergebnisse der Kompilierung zeigt.
raina77ow

1
Ich würde auch hinzufügen readonly, um sicherzustellen, dass TypeScript Sie warnt, wenn Sie jemals versucht haben, diese Felder neu public static readonly BOOK_SHELF_FULL
zuzuweisen

Antworten:


26

In was ist dieses TS-Snippet kompiliert (über 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;
});

Wie Sie sehen, werden beide definierten Eigenschaften public staticeinfach an die exportierte Funktion angehängt (als ihre Eigenschaften). Daher sollten sie zugänglich sein, solange Sie ordnungsgemäß auf die Funktion selbst zugreifen.


428

Wenn Sie etwas wollten, das sich in modernen Browsern eher wie ein statischer Konstantenwert verhält (da es nicht durch anderen Code geändert werden kann), können Sie getder LibraryKlasse einen einzigen Accessor hinzufügen (dies funktioniert nur für ES5 + -Browser und 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);

Wenn Sie es ausführen, werden Sie sehen, dass der Versuch, die BOOK_SHELF_NONEEigenschaft auf einen neuen Wert zu setzen, nicht funktioniert.

2.0

In TypeScript 2.0 können Sie readonlysehr ähnliche Ergebnisse erzielen:

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

Die Syntax ist etwas einfacher und offensichtlicher. Der Compiler verhindert jedoch Änderungen und nicht die Laufzeit (anders als im ersten Beispiel, in dem die Änderung wie gezeigt überhaupt nicht zulässig wäre).


26
Das ist so eine gute Antwort. Meine Codierung ist jetzt glücklicher.
Gonzofish

7
Diese Lösung hat jedoch einen Nachteil - Sie können diese dynamischen Werte nicht als type MyType: Library.BOOK_SHELF_NONE | Library.BOOK_SHELF_FULL;
Typeinschränkung verwenden

2
@AdrianMoisa - bitte stellen Sie eine neue Frage.
WiredPrairie

2
@ hex-bitte stell eine neue Frage.
WiredPrairie

1
Was ist mit Aufzählungen?
neugierig95

40

Sie können dies mit Namespaces wie folgt tun :

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

Dann können Sie es von einem anderen Ort importieren:

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

Wenn Sie dort auch eine Klasse benötigen, fügen Sie diese in den Namespace ein: export class Book {...}


2
Wenn ich die Konstanten bei der Initialisierung eines Objekts verwende, scheinen sie nicht erkannt zu werden. Zum Beispiel: { type: Library.BOOK_SHELF_NONE }scheint zu denken, dass der Namespace keinen Export aufgerufen hat BOOK_SHELF_NONE. Wenn ich jedoch nur eine lokale Variable mit der Referenz setze, wird sie problemlos aufgelöst. TS 2.2
Crush

13

In der Zwischenzeit kann dies durch einen Dekorateur in Kombination mit gelöst werden, Object.freezeoder Object.definePropertyich verwende dies, es ist ein bisschen hübscher als die Verwendung von Tonnen von Gettern. Sie können diesen TS-Spielplatz direkt kopieren / einfügen , um ihn in Aktion zu sehen. - Es gibt zwei Möglichkeiten


Machen Sie einzelne Felder "endgültig"

Der folgende Dekorator konvertiert sowohl kommentierte statische als auch nicht statische Felder in "Nur-Getter-Eigenschaften".

Hinweis : Wenn eine Instanzvariable ohne Anfangswert mit Anmerkungen versehen wird @final, ist der erste zugewiesene Wert (egal wann) der letzte.

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

Der Dekorateur: Stellen Sie sicher, dass Sie dies in Ihren Code aufnehmen!

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

Als Alternative zum obigen Dekorator würde es auch eine strikte Version davon geben, die sogar einen Fehler auslösen würde, wenn jemand versuchte, dem Feld "use strict";, das gesetzt wurde , einen Wert zuzuweisen . (Dies ist jedoch nur der statische Teil)

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

Machen Sie jedes statische Feld "endgültig"

Möglicher Nachteil: Dies funktioniert nur für ALLE Statiken dieser Klasse oder für keine, kann jedoch nicht auf bestimmte Statiken angewendet werden.

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

Ich denke, das ist der sauberste Ansatz. Vielen Dank!
Thibs

Eine kleine Ergänzung: if (!value)sollte sein if (value === undefined), statische Werte sein zu lassen ""oder 0.
Leon Adler

6

Vielen Dank, dass Sie WiredPrairie!

Um Ihre Antwort ein wenig zu erweitern, finden Sie hier ein vollständiges Beispiel für die Definition einer Konstantenklasse.

// CYConstants.ts

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

export = CYConstants;

Benutzen

// main.ts

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

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

2

Die folgende Lösung funktioniert auch ab 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"];

Benutzen:

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

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

Das ist wirklich cool, aber wenn Sie damit mehr als ein paar Werte definieren (wie Sie es in einer Aufzählung in anderen Sprachen tun könnten), müssen Sie nicht alle Werte einzeln importieren. Dies könnte sehr schnell umständlich werden!
Corey Larson

100% stimmten zu. Könnte in der Lage sein, sie zu benennen, aber ich habe es nicht untersucht. Vielleicht könnten Sie {ArrayConstants} aus "./Constants.ts" importieren und dann so etwas wie ArrayConstants.NotFound machen. Ich muss mehr damit spielen, wenn ich Zeit habe.
Andrew

Ja, Sie können Namespaces verwenden, um zu vermeiden, dass Werte einzeln verwendet werden. Lesen Sie hier meine eigene Antwort.
Ivan Castellanos

0

Einfach 'export' Variable und 'import' in Ihrer Klasse

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

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

Verwenden Sie es jetzt als,

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

0

Sie können einen Getter verwenden, damit Ihre Immobilie nur lesbar ist. Beispiel:

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

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

In einer anderen Klasse verwendet:

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

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

Dies macht das, was als Konstante erwartet wird, beschreibbar:let a = new MyClass(); a.STATUSES.level1 = 'not level1'; let plainWrong = new MyClass().STATUSES.level1; // 'not level1'
Leon Adler
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.