открытый статический констант в TypeScript


186

Есть ли такая вещь, как открытые статические константы в TypeScript? У меня есть класс, который выглядит так:

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

В этом классе я могу делать, Library.BOOK_SHELF_NONEи TSC не жалуется. Но если я попытаюсь использовать класс Library в другом месте и попытаться сделать то же самое, он не распознает его.


1
Можете ли вы показать пример использования этого класса в другом месте?
raina77ow

@ raina77ow Когда я начал добавлять код для того, как я его использую, я понял, что из-за пространства имен жаловался tsc. Если вы хотите добавить это как ответ, я могу пометить его как полный, так как вы помогли мне найти его. Я не очень люблю отвечать на свои вопросы.
Кристалл

Ах, это было причиной. ) Рад, что помог; Я добавлю ответ, показывающий результаты компиляции.
raina77ow

1
Я также добавил readonlyбы, чтобы TypeScript предупреждал вас, если вы когда-либо пытались переназначить эти поля:public static readonly BOOK_SHELF_FULL
Ян Молак,

Ответы:


26

Вот то, во что скомпилирован этот фрагмент TS (через 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;
});

Как видите, оба свойства, определенные как public static, просто присоединяются к экспортируемой функции (как ее свойства); поэтому они должны быть доступны, пока вы правильно обращаетесь к самой функции.


430

Если вам нужно что-то, что в современных браузерах ведет себя как значение статической константы (то, что оно не может быть изменено другим кодом), вы можете добавить getединственный Libraryдоступ к классу (это будет работать только для браузеров ES5 + и 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);

Если вы запустите его, вы увидите, как попытка установить для BOOK_SHELF_NONEсвойства новое значение не работает.

2,0

В TypeScript 2.0 вы можете использовать readonlyдля достижения очень похожих результатов:

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

Синтаксис немного проще и очевиднее. Однако компилятор предотвращает изменения, а не время выполнения (в отличие от первого примера, где изменение вообще не будет разрешено, как показано).


26
это такой хороший ответ. мое кодирование теперь стало лучше благодаря этому.
гонзофиш

7
Это решение имеет недостаток - вы не можете использовать эти динамические значения в качестве ограничения типа, например:type MyType: Library.BOOK_SHELF_NONE | Library.BOOK_SHELF_FULL;
Константин Колесниченко

2
@AdrianMoisa - пожалуйста, задайте новый вопрос.
WiredPrairie

2
@ hex - пожалуйста, задай новый вопрос.
WiredPrairie

1
Что насчет перечислений?
curious95

40

Вы можете сделать это, используя пространства имен , как это:

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

Затем вы можете импортировать его из любого места:

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

Если вам также нужен класс, включите его в пространство имен: export class Book {...}


2
Если я использую константы в инициализации объекта, они, похоже, не распознаются. Например: { type: Library.BOOK_SHELF_NONE }кажется, что в пространстве имен нет экспорта с именем BOOK_SHELF_NONE. Однако, если я просто установлю локальную переменную со ссылкой, она разрешится нормально. TS 2.2
дробить

13

Между тем это можно решить с помощью декоратора в сочетании с Object.freezeили Object.defineProperty, я использую это, это немного красивее, чем с помощью тонны геттеров. Вы можете скопировать / вставить это непосредственно TS Playground, чтобы увидеть его в действии. - есть два варианта


Сделайте отдельные поля "окончательными"

Следующий декоратор преобразует как аннотированные статические, так и нестатические поля в свойства «только для получения».

Примечание : Если переменная экземпляра без начального значения аннотирована @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.

Декоратор: убедитесь, что вы включили это в свой код!

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

В качестве альтернативы вышеприведенному декоратору также существует строгая версия этого, которая даже выдает ошибку, когда кто-то пытается присвоить какое-то значение полю с "use strict";установленным значением. (Это только статическая часть, хотя)

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

Сделайте каждое статическое поле "финальным"

Возможный недостаток: это будет работать только для ВСЕХ статик этого класса или ни для одного, но не может быть применено к конкретной статике.

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

Я думаю, что это самый чистый подход. Спасибо!
Thibs

Одно маленькое дополнение: if (!value)должно быть , if (value === undefined)чтобы значения статических быть ""или 0.
Леон Адлер

6

Спасибо WiredPrairie!

Чтобы немного расширить свой ответ, вот полный пример определения класса констант.

// CYConstants.ts

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

export = CYConstants;

Использовать

// main.ts

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

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

2

Следующее решение также работает с 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"];

Использовать:

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

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

Это действительно здорово, но если вы используете его для определения более пары значений (как вы могли бы в Enum на других языках), то вы застряли, импортируя все значения по отдельности. Это может стать очень громоздким и быстрым!
Кори Ларсон

100% согласились. Мог бы быть в состоянии именовать их, но я не изучал это. Может быть, вы могли бы импортировать {ArrayConstants} из "./Constants.ts", а затем сделать что-то вроде ArrayConstants.NotFound. Приходится играть с ним больше, когда у меня будет время.
Андрей

Да, вы можете использовать пространства имен, чтобы избежать использования значений по отдельности, прочитайте мой собственный ответ здесь.
Иван Кастелланос

0

Просто «экспортируйте» переменную и «импортируйте» в свой класс

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

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

Теперь используйте его как,

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

0

Вы можете использовать геттер, так что ваша собственность будет только для чтения. Пример:

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

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

Используется в другом классе:

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

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

Это делает то, что ожидается постоянным для записи:let a = new MyClass(); a.STATUSES.level1 = 'not level1'; let plainWrong = new MyClass().STATUSES.level1; // 'not level1'
Леон Адлер,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.