const estática pública en TypeScript

186

¿Existen las constantes estáticas públicas en TypeScript? Tengo una clase que se parece a:

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

En esa clase, puedo hacerlo Library.BOOK_SHELF_NONEy el tsc no se queja. Pero si trato de usar la biblioteca de clases en otro lugar y trato de hacer lo mismo, no lo reconoce.

Cristal
fuente
1
¿Puedes mostrar un ejemplo del uso de esa clase en otro lugar?
raina77ow
@ raina77ow Cuando comencé a agregar el código de cómo lo uso, me di cuenta de que es debido al espacio de nombres que el tsc se quejó. Si quieres poner eso como respuesta, puedo marcarlo como completo ya que me ayudaste a encontrarlo. Realmente no me gusta responder mis propias preguntas.
Cristal
Ah, esa fue la causa. ) Me alegro de haber ayudado; Agregaré la respuesta que muestra los resultados de la compilación.
raina77ow
1
También agregaría readonlypara asegurarme de que TypeScript le avisará si alguna vez intentó reasignar esos campos:public static readonly BOOK_SHELF_FULL
Jan Molak

Respuestas:

26

Esto es lo que compila este fragmento de TS (a través de 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 puede ver, ambas propiedades definidas como public staticsimplemente se adjuntan a la función exportada (como sus propiedades); por lo tanto, deben ser accesibles siempre que acceda correctamente a la función en sí.

raina77ow
fuente
430

Si quisiera algo que se comportara más como un valor constante estático en los navegadores modernos (en el sentido de que no puede ser cambiado por otro código), podría agregar un getsolo acceso a la Libraryclase (esto solo funcionará para los navegadores ES5 + y 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);

Si lo ejecuta, verá cómo el intento de establecer la BOOK_SHELF_NONEpropiedad en un nuevo valor no funciona.

2,0

En TypeScript 2.0, puede usar readonlypara lograr resultados muy similares:

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

La sintaxis es un poco más simple y más obvia. Sin embargo, el compilador evita cambios en lugar del tiempo de ejecución (a diferencia del primer ejemplo, donde el cambio no se permitiría en absoluto como se demostró).

WiredPrairie
fuente
26
Esta es una buena respuesta. mi codificación ahora es más feliz por eso.
gonzofish
77
Sin embargo, esta solución tiene un inconveniente: no puede utilizar estos valores dinámicos como restricción de tipo, por ejemplo:type MyType: Library.BOOK_SHELF_NONE | Library.BOOK_SHELF_FULL;
Kostiantyn Kolesnichenko
2
@AdrianMoisa: haz una nueva pregunta.
WiredPrairie
2
@ hex-por favor haga una nueva pregunta.
WiredPrairie
1
¿Qué hay de las enumeraciones?
curioso95
40

Puede hacerlo usando espacios de nombres , como este:

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

Luego puede importarlo desde cualquier otro lugar:

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

Si necesita una clase allí, inclúyala dentro del espacio de nombres: export class Book {...}

Ivan Castellanos
fuente
2
Si estoy usando las constantes en la inicialización de un objeto, no parecen ser reconocidas. Por ejemplo: { type: Library.BOOK_SHELF_NONE }parece pensar que el espacio de nombres no tiene llamada a la exportación BOOK_SHELF_NONE. Sin embargo, si solo establezco una variable local con la referencia, la resuelve bien. TS 2.2
aplastar
13

Mientras tanto, esto se puede resolver a través de un decorador en combinación con Object.freezeo Object.defineProperty, estoy usando esto, es un poco más bonito que usar toneladas de getters. Puede copiar / pegar este TS Playground directamente para verlo en acción. - hay dos opciones


Hacer que los campos individuales sean "finales"

El siguiente decorador convierte los campos anotados estáticos y no estáticos en "propiedades de solo captador".

Nota : Si se anota una variable de instancia sin valor inicial @final, el primer valor asignado (sin importar cuándo) será el último.

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

El decorador: ¡asegúrese de incluir esto en su 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 al decorador anterior, también habría una versión estricta de esto, que incluso arrojaría un Error cuando alguien intentara asignar algún valor al campo con el "use strict";ajuste. (Sin embargo, esta es solo la 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
    });
}

Haga que cada campo estático sea "final"

Posible inconveniente: Esto solo funcionará para TODAS las estadísticas de esa clase o para ninguna, pero no se puede aplicar a estadísticas 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."
olsn
fuente
Creo que este es el enfoque más limpio. ¡Gracias!
Thibs
Una pequeña adición: if (!value)debe ser if (value === undefined)dejar que los valores estáticos sean ""o 0.
Leon Adler
6

¡Gracias WiredPrairie!

Solo para ampliar un poco su respuesta, aquí hay un ejemplo completo de cómo definir una clase 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!)
Andrés
fuente
2

La siguiente solución también funciona a partir de 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!!!");
}
Andrés
fuente
Esto es realmente genial, pero si lo está utilizando para definir más de un par de valores (como lo haría en una enumeración en otros idiomas), entonces está atascado importando todos los valores individualmente. ¡Esto podría volverse engorroso realmente rápido!
Corey Larson
100% de acuerdo. Podría ser capaz de nombrarlos pero no lo he investigado. Tal vez podría importar {ArrayConstants} desde "./Constants.ts" y luego hacer algo como ArrayConstants.NotFound. Tengo que jugar más cuando tengo tiempo.
Andrew
Sí, puedes usar espacios de nombres para evitar usar valores individualmente, lee mi propia respuesta aquí.
Ivan Castellanos
0

Simplemente 'exportar' variable e 'importar' en su clase

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

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

Ahora úsalo como,

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

Puede usar un captador para que su propiedad sea de solo lectura. Ejemplo:

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

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

Utilizado en otra clase:

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

    tryLevel() {
       console.log(this.myClass.STATUSES.level1);
    }
}
silvanasono
fuente
Esto hace que lo que se espera sea una escritura constante:let a = new MyClass(); a.STATUSES.level1 = 'not level1'; let plainWrong = new MyClass().STATUSES.level1; // 'not level1'
Leon Adler