public static const dans TypeScript


186

Existe-t-il des constantes statiques publiques dans TypeScript? J'ai une classe qui ressemble à:

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

Dans cette classe, je peux faire Library.BOOK_SHELF_NONEet le tsc ne se plaint pas. Mais si j'essaye d'utiliser la classe Library ailleurs et que j'essaie de faire la même chose, elle ne la reconnaît pas.


1
Pouvez-vous montrer un exemple d'utilisation de cette classe ailleurs?
— raina77ow

@ raina77ow Lorsque j'ai commencé à ajouter du code sur la façon dont je l'utilise, j'ai réalisé que c'était à cause de l'espace de noms que le tsc se plaignait. Si vous voulez mettre cela comme réponse, je peux le marquer comme complet puisque vous m'avez aidé à le trouver. Je n'aime pas vraiment répondre à mes propres questions.
— Crystal

Ah, c'était la cause. ) Heureux d'avoir aidé; J'ajouterai la réponse montrant les résultats de la compilation.
— raina77ow

1
J'ajouterais également readonlypour m'assurer que TypeScript vous avertira si vous avez déjà essayé de réattribuer ces champs:public static readonly BOOK_SHELF_FULL
— Jan Molak

Réponses:


26

Voici dans quoi cet extrait TS est compilé (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;
});

Comme vous le voyez, les deux propriétés définies comme public staticsont simplement attachées à la fonction exportée (en tant que ses propriétés); par conséquent, ils doivent être accessibles tant que vous accédez correctement à la fonction elle-même.


430

Si vous voulez quelque chose qui se comporte plus comme une valeur constante statique dans les navigateurs modernes (en ce sens qu'il ne peut pas être modifié par un autre code), vous pouvez ajouter un getseul accesseur à la Libraryclasse (cela ne fonctionnera que pour les navigateurs ES5 + et 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 vous l'exécutez, vous verrez comment la tentative de définition de la BOOK_SHELF_NONEpropriété sur une nouvelle valeur ne fonctionne pas.

2.0

Dans TypeScript 2.0, vous pouvez utiliser readonlypour obtenir des résultats très similaires:

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

La syntaxe est un peu plus simple et plus évidente. Cependant, le compilateur empêche les modifications plutôt que le temps d'exécution (contrairement au premier exemple, où la modification ne serait pas autorisée du tout, comme illustré).


26
c'est une si bonne réponse. mon codage est maintenant plus heureux grâce à cela.
— gonzofish

7
Cette solution présente cependant un inconvénient - vous ne pouvez pas utiliser ces valeurs dynamiques comme contrainte de type, par exemple:type MyType: Library.BOOK_SHELF_NONE | Library.BOOK_SHELF_FULL;
— Kostiantyn Kolesnichenko

2
@AdrianMoisa - veuillez poser une nouvelle question.
— WiredPrairie

2
@ hex-veuillez poser une nouvelle question.
— WiredPrairie

1
Et les énumérations?
— curious95

40

Vous pouvez le faire en utilisant des espaces de noms , comme ceci:

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

Ensuite, vous pouvez l'importer de n'importe où ailleurs:

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

Si vous avez besoin d'une classe, incluez-la également dans l'espace de noms: export class Book {...}


2
Si j'utilise les constantes lors de l'initialisation d'un objet, elles ne semblent pas être reconnues. Par exemple: { type: Library.BOOK_SHELF_NONE }semble penser que l'espace de noms n'a pas d'export appelé BOOK_SHELF_NONE. Cependant, si je viens de définir une variable locale avec la référence, cela résout le problème. TS 2.2
— écraser

13

Pendant ce temps, cela peut être résolu par un décorateur en combinaison avec Object.freezeou Object.defineProperty, j'utilise ceci, c'est un peu plus joli que d'utiliser des tonnes de getters. Vous pouvez copier / coller directement ce TS Playground pour le voir en action. - Il y a deux options


Rendre les champs individuels "définitifs"

Le décorateur suivant convertit les champs statiques annotés et non statiques en "propriétés de lecture uniquement".

Remarque : Si une variable d'instance sans valeur initiale est annotée @final, la première valeur attribuée (peu importe quand) sera la dernière.

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

Le décorateur: assurez-vous de l'inclure dans votre code!

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

Comme alternative au décorateur ci-dessus, il y aurait également une version stricte de celui-ci, qui lèverait même une erreur lorsque quelqu'un essaierait d'attribuer une valeur au champ en "use strict";cours de définition. (Ce n'est cependant que la partie statique)

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

Rendre chaque champ statique "définitif"

Inconvénient possible: cela ne fonctionnera que pour TOUTES les statiques de cette classe ou pour aucune, mais ne peut pas être appliqué à des statiques spécifiques.

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

Je pense que c'est l'approche la plus propre. Merci!
— Thibs

Un petit ajout: if (!value)devrait être if (value === undefined)de laisser les valeurs statiques être ""ou 0.
— Leon Adler

6

Merci WiredPrairie!

Pour développer un peu votre réponse, voici un exemple complet de définition d'une classe de constantes.

// CYConstants.ts

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

export = CYConstants;

Utiliser

// main.ts

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

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

2

La solution suivante fonctionne également à 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"];

Utiliser:

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

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

C'est vraiment cool, mais si vous l'utilisez pour définir plus de deux valeurs (comme vous le feriez dans un Enum dans d'autres langues), vous êtes bloqué en important l'importation de toutes les valeurs individuellement. Cela pourrait devenir fastidieux très vite!
— Corey Larson

100% d'accord. Pourrait être en mesure de les espaces de noms, mais je ne l'ai pas regardé. Vous pourriez peut-être importer {ArrayConstants} à partir de "./Constants.ts" puis faire quelque chose comme ArrayConstants.NotFound. Je dois jouer avec plus quand j'ai le temps.
— Andrew

Ouais, vous pouvez utiliser des espaces de noms pour éviter d'utiliser des valeurs individuellement, lisez ma propre réponse ici.
— Ivan Castellanos

0

Simplement «exporter» la variable et «importer» dans votre classe

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

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

Maintenant, utilisez-le comme,

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

0

Vous pouvez utiliser un getter pour que votre propriété soit en lecture seule. Exemple:

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

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

Utilisé dans une autre classe:

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

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

Cela rend ce qui devrait être une constante inscriptible:let a = new MyClass(); a.STATUSES.level1 = 'not level1'; let plainWrong = new MyClass().STATUSES.level1; // 'not level1'
— Leon Adler
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.