const estática pública em TypeScript


186

Existe algo como constantes estáticas públicas no TypeScript? Eu tenho uma classe que se parece com:

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

Nessa aula, eu posso fazer Library.BOOK_SHELF_NONEe o tsc não reclama. Mas se eu tentar usar a biblioteca de classes em outro lugar e tentar fazer a mesma coisa, ela não a reconhecerá.


1
Você pode mostrar um exemplo de como usar essa classe em outro lugar?
raina77ow

@ raina77ow Quando comecei a adicionar o código de como usá-lo, percebi que é por causa do namespace que o tsc reclamou. Se você quiser colocar isso como resposta, posso marcar como completo, pois você me ajudou a encontrá-lo. Eu realmente não gosto de responder minhas próprias perguntas.
Crystal

Ah, essa foi a causa. ) Ainda bem que ajudei; Vou adicionar a resposta mostrando os resultados da compilação.
raina77ow

1
Eu também acrescentaria readonlypara garantir que o TypeScript o avise se você tentar reatribuir esses campos:public static readonly BOOK_SHELF_FULL
Jan Molak 18/02/19

Respostas:


26

Aqui está o que esse snippet TS compilou (via 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;
});

Como você vê, ambas as propriedades definidas public staticsão simplesmente anexadas à função exportada (como suas propriedades); portanto, eles devem estar acessíveis desde que você acesse adequadamente a própria função.


430

Se você quiser algo que se comporte mais como um valor constante estático nos navegadores modernos (na medida em que não pode ser alterado por outro código), adicione um getúnico acessador à Libraryclasse (isso funcionará apenas nos navegadores 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 você executá-lo, verá como a tentativa de definir a BOOK_SHELF_NONEpropriedade com um novo valor não funciona.

2.0

No TypeScript 2.0, você pode usar readonlypara obter resultados muito semelhantes:

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

A sintaxe é um pouco mais simples e mais óbvia. No entanto, o compilador evita alterações em vez do tempo de execução (diferente do primeiro exemplo, onde a alteração não seria permitida, como demonstrado).


26
Esta é uma resposta tão boa. minha codificação agora está mais feliz por causa disso.
21715 Gonzofish

7
Esta solução tem uma desvantagem - você não pode usar esses valores dinâmicos como restrição de tipo, por exemplo:type MyType: Library.BOOK_SHELF_NONE | Library.BOOK_SHELF_FULL;
Kostiantyn Kolesnichenko 15/16

2
@AdrianMoisa - faça uma nova pergunta.
WiredPrairie

2
@ hex-por favor, faça uma nova pergunta.
WiredPrairie

1
E as enums?
Home /

40

Você pode fazer isso usando namespaces , assim:

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

Então você pode importá-lo de qualquer outro lugar:

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

Se você precisar de uma classe lá, inclua-a no espaço para nome: export class Book {...}


2
Se estou usando as constantes na inicialização de um objeto, elas não parecem ser reconhecidas. Por exemplo: { type: Library.BOOK_SHELF_NONE }parece pensar que o espaço para nome não possui exportação chamada BOOK_SHELF_NONE. No entanto, se eu apenas definir uma variável local com a referência, ela a resolverá bem. TS 2.2
esmagar

13

Enquanto isso, isso pode ser resolvido através de um decorador em combinação com , Object.freezeou Object.defineProperty, eu estou usando isso, é um pouco mais bonito do que usar toneladas de getters. Você pode copiar / colar isso diretamente no TS Playground para vê-lo em ação. - Existem duas opções


Tornar os campos individuais "finais"

O decorador a seguir converte os campos estáticos e não estáticos anotados em "propriedades getter-only".

Nota : Se uma variável de instância sem valor inicial for anotada @final, o primeiro valor designado (não importa quando) será o 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.

O decorador: inclua isso no seu código!

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

Como alternativa ao decorador acima, também haveria uma versão estrita disso, que geraria um erro quando alguém tentasse atribuir algum valor ao campo ao "use strict";ser definido. (Esta é apenas a parte estática)

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

Torne todos os campos estáticos "finais"

Possível desvantagem: isso funcionará apenas para TODAS as estáticas dessa classe ou para nenhuma, mas não pode ser aplicado a estáticas específicas.

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

Eu acho que essa é a abordagem mais limpa. Obrigado!
Thibs

Uma pequena adição: if (!value)deve ser if (value === undefined)deixar valores estáticos serem ""ou 0.
Leon Adler

6

Obrigado WiredPrairie!

Apenas para expandir um pouco sua resposta, aqui está um exemplo completo de definição de uma classe de constantes.

// CYConstants.ts

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

export = CYConstants;

Usar

// main.ts

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

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

2

A solução a seguir também funciona a partir do 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"];

Usar:

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

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

Isso é muito legal, mas se você o estiver usando para definir mais do que alguns valores (como você pode fazer em um Enum em outros idiomas), ficará sem importar todos os valores individualmente. Isso pode ficar complicado muito rápido!
Corey Larson

100% concordaram. Pode ser capaz de namespace-los, mas eu não olhei para ele. Talvez você possa importar {ArrayConstants} de "./Constants.ts" e faça algo como ArrayConstants.NotFound. Tenho que brincar mais quando tiver tempo.
30516 Andrew

Sim, você pode usar espaços para nome para evitar o uso de valores individualmente, leia minha própria resposta aqui.
Ivan Castellanos

0

Simplesmente 'exporte' a variável e 'importe' na sua classe

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

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

Agora use-o como,

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

0

Você pode usar um getter, para que sua propriedade seja somente leitura. Exemplo:

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

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

Usado em outra classe:

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

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

Isso faz com que o que é esperado para ser um gravável constante:let a = new MyClass(); a.STATUSES.level1 = 'not level1'; let plainWrong = new MyClass().STATUSES.level1; // 'not level1'
Leon Adler
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.