загальнодоступний статичний const у 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 не скаржиться. Але якщо я спробую використати бібліотеку класів в іншому місці і спробувати зробити те саме, вона не розпізнає.


1
Чи можете ви показати приклад використання цього класу в іншому місці?
— raina77ow

@ raina77ow Коли я почав додавати в код про те, як ним користуватися, я зрозумів, що ЦСК скаржився саме на пробіг імен. Якщо ви хочете поставити це як відповідь, я можу відзначити його як повне, оскільки ви допомогли мені знайти його. Мені не дуже подобається відповідати на власні запитання.
— Кришталь

Так, це було причиною. ) Радий, що допоміг; Я додам відповідь, що показує результати компіляції.
— 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
це така гарна відповідь. моє кодування зараз щасливіше через це.
— gonzofish

7
Однак у цього рішення є недолік - ви не можете використовувати ці динамічні значення як обмеження типу, наприклад:type MyType: Library.BOOK_SHELF_NONE | Library.BOOK_SHELF_FULL;
— Костянтин Колесніченко

2
@AdrianMoisa - будь ласка, задайте нове запитання.
— WiredPrairie

2
@ hex - будь ласка, задайте нове запитання.
— WiredPrairie

1
А як із переліками?
— цікаво95

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, щоб побачити його в дії. - Є два варіанти


Зробити окремі поля "остаточними"

Наступний декоратор перетворює як анотовані статичні, так і нестатичні поля у "властивості лише для отримання".

Примітка . Якщо анотація змінної примірника, яка не має початкового значення @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.