publiczna stała statyczna w TypeScript


186

Czy istnieje coś takiego jak publiczne stałe statyczne w TypeScript? Mam zajęcia, które wyglądają jak:

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

W tej klasie mogę to zrobić, Library.BOOK_SHELF_NONEa tsc nie narzeka. Ale jeśli spróbuję użyć biblioteki klas w innym miejscu i spróbuję zrobić to samo, nie rozpozna tego.


1
Czy możesz pokazać przykład użycia tej klasy w innym miejscu?
raina77ow

@ raina77ow Gdy zacząłem dodawać kod, w jaki sposób go używam, zdałem sobie sprawę, że to z powodu przestrzeni nazw narzekał tsc. Jeśli chcesz podać to jako odpowiedź, mogę oznaczyć je jako kompletne, ponieważ pomogłeś mi je znaleźć. Naprawdę nie lubię odpowiadać na własne pytania.
Kryształ

Ach, to była przyczyna. ) Cieszę się, że pomogłem; Dodam odpowiedź pokazującą wyniki kompilacji.
raina77ow

1
Dodam również, readonlyaby upewnić się, że TypeScript ostrzeże Cię, jeśli kiedykolwiek spróbujesz zmienić przypisanie tych pól:public static readonly BOOK_SHELF_FULL
Jan Molak

Odpowiedzi:


26

Oto, co jest wkompilowane w ten fragment kodu TS (przez 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;
});

Jak widać, obie właściwości zdefiniowane jako public staticsą po prostu dołączone do eksportowanej funkcji (jako jej właściwości); dlatego powinny być dostępne tak długo, jak prawidłowo uzyskujesz dostęp do samej funkcji.


430

Jeśli chcesz czegoś, co zachowuje się bardziej jak stała wartość statyczna we współczesnych przeglądarkach (ponieważ nie można tego zmienić za pomocą innego kodu), możesz dodać getjedyny akcesor do Libraryklasy (będzie to działać tylko w przeglądarkach 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);

Jeśli go uruchomisz, zobaczysz, jak próba ustawienia BOOK_SHELF_NONEwłaściwości na nową wartość nie działa.

2.0

W TypeScript 2.0 można użyć readonlydo uzyskania bardzo podobnych wyników:

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

Składnia jest nieco prostsza i bardziej oczywista. Jednak kompilator zapobiega zmianom, a nie czasowi działania (w przeciwieństwie do pierwszego przykładu, w którym zmiana nie byłaby w ogóle dozwolona, ​​jak pokazano).


26
to taka dobra odpowiedź. dzięki temu moje kodowanie jest teraz szczęśliwsze.
gonzofish,

7
To rozwiązanie ma jednak wadę - nie można użyć tych wartości dynamicznych jako ograniczenia typu, na przykład:type MyType: Library.BOOK_SHELF_NONE | Library.BOOK_SHELF_FULL;
Kostiantyn Kolesnichenko

2
@AdrianMoisa - zadaj nowe pytanie.
WiredPrairie

2
@ hex-proszę zadać nowe pytanie.
WiredPrairie

1
Co ze śliwkami?
ciekawy95

40

Możesz to zrobić za pomocą przestrzeni nazw , takich jak to:

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

Następnie możesz go zaimportować z dowolnego miejsca:

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

Jeśli potrzebujesz klasy, dołącz ją również do przestrzeni nazw: export class Book {...}


2
Jeśli używam stałych do inicjalizacji obiektu, wydaje się, że nie są one rozpoznawane. Na przykład: { type: Library.BOOK_SHELF_NONE }wydaje się sądzić, że przestrzeń nazw nie ma wywołania eksportu BOOK_SHELF_NONE. Jeśli jednak ustawię zmienną lokalną za pomocą odwołania, rozwiąże to dobrze. TS 2.2
zniszczyć

13

Tymczasem można to rozwiązać za pomocą dekoratora w połączeniu z, Object.freezelub Object.defineProperty, używam tego, jest to trochę ładniejsze niż używanie ton getterów. Możesz skopiować / wkleić ten bezpośrednio TS Playground, aby zobaczyć go w akcji. - Istnieją dwie opcje


Ustaw poszczególne pola jako „końcowe”

Poniższy dekorator konwertuje zarówno statyczne, jak i niestatyczne pola z adnotacjami na „właściwości tylko gettera”.

Uwaga : Jeśli adnotacja ma zmienna instancji bez wartości początkowej @final, to pierwsza przypisana wartość (bez względu na to, kiedy) będzie ostateczna.

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

Dekorator: upewnij się, że umieściłeś to w swoim kodzie!

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

Alternatywą dla powyższego dekoratora byłaby też jego ścisła wersja, która rzucałaby nawet Błąd, gdy ktoś próbował przypisać jakąś wartość do pola z "use strict";ustawionym. (Jest to jednak tylko część statyczna)

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

Niech każde pole statyczne będzie „ostateczne”

Możliwe wady: Będzie to działać tylko dla WSZYSTKICH statyki tej klasy lub dla żadnej, ale nie może być zastosowane do konkretnej statyki.

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

Myślę, że to najczystsze podejście. Dzięki!
Thibs

Jednym małym dodatkiem: if (!value)powinno być if (value === undefined)pozwolenie, aby wartościami statycznymi były ""lub 0.
Leon Adler

6

Dziękuję WiredPrairie!

Aby nieco rozwinąć twoją odpowiedź, oto kompletny przykład definiowania klasy stałych.

// CYConstants.ts

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

export = CYConstants;

Używać

// main.ts

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

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

2

Poniższe rozwiązanie działa również od wersji 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"];

Używać:

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

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

To jest naprawdę fajne, ale jeśli używasz go do zdefiniowania więcej niż kilku wartości (jak w Enum w innych językach), to utkniesz w imporcie wszystkich wartości osobno. To może stać się niewygodne naprawdę szybko!
Corey Larson

100% uzgodnione. Być może uda mi się je nazwać, ale nie zajrzałem do tego. Może możesz zaimportować {ArrayConstants} z „./Constants.ts”, a następnie zrobić coś takiego jak ArrayConstants.NotFound. Muszę grać z tym więcej, kiedy będę mieć czas.
Andrew

Tak, możesz użyć przestrzeni nazw, aby uniknąć używania wartości indywidualnie, przeczytaj moją własną odpowiedź tutaj.
Ivan Castellanos

0

Wystarczy po prostu „wyeksportować” zmienną i „zaimportować” do swojej klasy

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

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

Teraz użyj go jako

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

0

Możesz użyć gettera, dzięki czemu Twoja nieruchomość będzie tylko czytać. Przykład:

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

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

Używany w innej klasie:

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

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

To sprawia, że ​​to, co ma być zapisywane w sposób ciągły:let a = new MyClass(); a.STATUSES.level1 = 'not level1'; let plainWrong = new MyClass().STATUSES.level1; // 'not level1'
Leon Adler
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.