Klasy statyczne TypeScript

145

Chciałem przejść do TypeScript z tradycyjnego JS, ponieważ podoba mi się składnia podobna do C #. Mój problem polega na tym, że nie mogę dowiedzieć się, jak zadeklarować klasy statyczne w TypeScript.

W języku C # często używam klas statycznych do organizowania zmiennych i metod, umieszczając je razem w nazwanej klasie, bez konieczności tworzenia obiektu. W waniliowym JS robiłem to za pomocą prostego obiektu JS:

var myStaticClass = {
    property: 10,
    method: function(){}
}

W TypeScript wolałbym raczej wybrać moje podejście C-sharpy, ale wydaje się, że klasy statyczne nie istnieją w TS. Jakie jest właściwe rozwiązanie tego problemu?

Rayjax
źródło

Odpowiedzi:

166

TypeScript nie jest C #, więc nie należy oczekiwać tych samych koncepcji języka C # w TypeScript. Pytanie brzmi, dlaczego chcesz klas statycznych?

W języku C # klasa statyczna jest po prostu klasą, której nie można tworzyć podklasy i musi zawierać tylko metody statyczne. C # nie pozwala na definiowanie funkcji poza klasami. W TypeScript jest to jednak możliwe.

Jeśli szukasz sposobu na umieszczenie funkcji / metod w przestrzeni nazw (tj. Nie globalnej), możesz rozważyć użycie modułów TypeScript, np.

module M {
    var s = "hello";
    export function f() {
        return s;
    }
}

Abyś mógł uzyskać dostęp do Mf () z zewnątrz, ale nie s, i nie możesz rozszerzyć modułu.

Aby uzyskać więcej informacji, zobacz specyfikację TypeScript .

Marcus
źródło
więc moduł może mieć metodę statyczną, ale klasa nie może? ale moduły nie mogą mieć danych statycznych. Nie jest tak wygodne jak JS do pakowania danych i kodu bez konieczności tworzenia jakiejkolwiek instancji.
dcsan
Przydatne może być uwzględnienie tego, że musisz uwzględnić .jsw swoim html. Więc Angular 2prawdopodobnie używasz System... tak zrobisz System.import("Folder/M");(lub jakąkolwiek ścieżkę do skompilowanego .jspliku) przedbootstrap import
Serj Sagan,
11
To jest przestarzałe. Również tslint nie pozwoli ci już tego robić dla modułów i przestrzeni nazw. Przeczytaj tutaj: palantir.github.io/tslint/rules/no-namespace
Florian Leitgeb
Mam przypadek użycia dla klas statycznych. W tej chwili mam klasę, która zawiera tylko metody statyczne. W projekcie musimy zapewnić jakąś konfigurację dla każdej klasy, której można użyć. Zadeklarowanie takiej klasy jako statycznej nie tylko pomogłoby mi zauważyć, że nie zostanie ona utworzona, ale także pozwoli uniknąć dodawania do niej elementów instancji przez innych programistów.
mdarefull
@florian leitgeb jaki jest zatem preferowany sposób, klasa zawierająca tylko metody statyczne i / lub słowo kluczowe abstract? To po prostu wydaje się kiepskie w porównaniu z modułem, który teraz wydaje się przestarzały
wired00
182

Klasy abstrakcyjne były pierwszorzędnym obywatelem języka TypeScript od czasu TypeScript 1.6. Nie można utworzyć wystąpienia klasy abstrakcyjnej.

Oto przykład:

export abstract class MyClass {         
    public static myProp = "Hello";

    public static doSomething(): string {
      return "World";
    }
}

const okay = MyClass.doSomething();

//const errors = new MyClass(); // Error
Fenton
źródło
6
W przypadku klas statycznych jest to najlepsza odpowiedź i głosuję za tym. Singletondotyczy wzorca pamięci współdzielonej tej samej instancji klasy. Ponadto klasa statyczna z definicji nie ma instancji, więc jeśli klient próbuje ją zainicjować, należy zgłosić wyjątek.
loretoparisi
1
Czy możemy stworzyć klasę abstrakcyjną?
KimchiMan
@KimchiMan - tak, abstractsłowo kluczowe jest obsługiwane w TypeScript.
Fenton
1
Zaktualizowano do użycia abstract! @KimchiMan - świetny pomysł!
Obsidian
3
Czy to lepsze podejście niż oznaczanie konstruktora jako prywatnego?
Joro Tenev,
72

Definiowanie statycznych właściwości i metod klasy jest opisane w 8.2.1 Specyfikacji języka Typescript :

class Point { 
  constructor(public x: number, public y: number) { } 
  public distance(p: Point) { 
    var dx = this.x - p.x; 
    var dy = this.y - p.y; 
    return Math.sqrt(dx * dx + dy * dy); 
  } 
  static origin = new Point(0, 0); 
  static distance(p1: Point, p2: Point) { 
    return p1.distance(p2); 
  } 
}

gdzie Point.distance()jest metodą statyczną (lub „klasową”).

Rob Raisch
źródło
14
To pokazuje, jak stworzyć metodę statyczną , ale nie odpowiada na pytanie, które dotyczy klas statycznych (chyba że prawdziwe pytanie dotyczy metod statycznych).
Marcus
18
Dziękuję za komentarz. Opisuje, jak tworzyć statyczne właściwości i metody, które razem pozwalają stworzyć klasę z danymi i funkcjonalnością bez potrzeby tworzenia instancji. Chociaż nie jest to „klasa statyczna”, spełnia wymagania opisane w przykładzie JavaScript w OP.
Rob Raisch
c # nie miał klas statycznych aż do wersji 2. istnieją one w języku c # tylko po to, aby zapobiec ich tworzeniu. nie możesz tego zrobić za pomocą javascript, więc nie ma to większego sensu
Simon_Weaver,
4
@Simon_Weaver Czego nie możesz zrobić w javascript? Zablokować tworzenie instancji klas? Maszynopis i tak nie przejmuje się zbytnio uruchomieniem, o ile pojawia się błąd kompilacji, gdy próbujesz zrobić coś, na co nie masz pozwolenia, to wszystko, czego potrzebujemy.
Alex
Myślę, że chodziło mi o to, że „nie mieliśmy statycznego SŁOWA KLUCZOWEGO na poziomie klasy (co oznacza, że ​​nie można utworzyć instancji klasy)” aż do wersji 2., ale czytając go ponownie, myślę, że mój komentarz pomijał w każdym razie punkt. OP tak naprawdę nie szukał „statycznego słowa kluczowego” dla całej klasy
Simon_Weaver
20

To pytanie jest dość przestarzałe, ale chciałem zostawić odpowiedź, która wykorzystuje aktualną wersję języka. Niestety, klasy statyczne nadal nie istnieją w TypeScript, jednak można napisać klasę, która zachowuje się podobnie z niewielkim narzutem, używając prywatnego konstruktora, który zapobiega tworzeniu instancji klas z zewnątrz.

class MyStaticClass {
    public static readonly property: number = 42;
    public static myMethod(): void { /* ... */ }
    private constructor() { /* noop */ }
}

Ten fragment pozwoli ci na użycie "statycznych" klas podobnych do ich odpowiedników w C #, z tą jedyną wadą, że nadal jest możliwe utworzenie ich od wewnątrz. Na szczęście nie można jednak rozszerzać klas za pomocą prywatnych konstruktorów.

Christian Ivicevic
źródło
9

To jest jeden sposób:

class SomeClass {
    private static myStaticVariable = "whatever";
    private static __static_ctor = (() => { /* do static constructor stuff :) */ })();
}

__static_ctortutaj jest natychmiast wywołane wyrażenie funkcyjne. Typescript wyświetli kod wywołujący go na końcu wygenerowanej klasy.

Aktualizacja: W przypadku typów ogólnych w konstruktorach statycznych, do których statyczne elementy członkowskie nie mogą już odwoływać się, potrzebujesz teraz dodatkowego kroku:

class SomeClass<T> {
    static myStaticVariable = "whatever";
    private ___static_ctor = (() => { var someClass:SomeClass<T> ; /* do static constructor stuff :) */ })();
    private static __static_ctor = SomeClass.prototype.___static_ctor();
}

W każdym razie, oczywiście, możesz po prostu wywołać konstruktor statyczny typu ogólnego po klasie, na przykład:

class SomeClass<T> {
    static myStaticVariable = "whatever";
    private __static_ctor = (() => { var example: SomeClass<T>; /* do static constructor stuff :) */ })();
}
SomeClass.prototype.__static_ctor();

Pamiętaj tylko, aby NIGDY nie używać thisw __static_ctorpowyższym (oczywiście).

James Wilkins
źródło
W ten sposób nadal emituje konstruktor dla klasy.
theycallmemorty
1
„This way” to hack i nie zmienia normalnego działania kompilatora, zgodnie z oczekiwaniami. Nawet class SomeClass {}generuje konstruktora - nie warto go komentować, jakby wprowadzono nowy numer. ;) FYI: Nie ma prawdziwych "konstruktorów" w JS - tylko funkcje, które mają "this", gdy są wywoływane na obiektach lub via new. To istnieje bez względu na każdą „klasę”.
James Wilkins,
6

Dzisiaj (31/07/2018) otrzymałem ten sam przypadek użycia i stwierdziłem, że jest to obejście. Jest oparty na moich badaniach i zadziałał dla mnie. Oczekiwanie - aby osiągnąć następujące wyniki w języku TypeScript:

var myStaticClass = {
    property: 10,
    method: function(){} 
}

Ja to zrobiłem:

//MyStaticMembers.ts
namespace MyStaticMembers {
        class MyStaticClass {
           static property: number = 10;
           static myMethod() {...}
        }
        export function Property(): number {
           return MyStaticClass.property;
        }
        export function Method(): void {
           return MyStaticClass.myMethod();
        }
     }

Dlatego konsumujemy go jak poniżej:

//app.ts
/// <reference path="MyStaticMembers.ts" />
    console.log(MyStaticMembers.Property);
    MyStaticMembers.Method();

To zadziałało dla mnie. Jeśli ktoś ma inne lepsze sugestie, niech nas wszyscy o tym posłuchają !!! Dzięki...

KoRa
źródło
3

Klasy statyczne w językach takich jak C # istnieją, ponieważ nie ma innych konstrukcji najwyższego poziomu do grupowania danych i funkcji. Jednak w JavaScript tak się dzieje, więc o wiele bardziej naturalne jest po prostu zadeklarowanie obiektu, tak jak to zrobiłeś. Aby dokładniej naśladować składnię klasy, możesz zadeklarować takie metody:

const myStaticClass = {
    property: 10,

    method() {

    }
}
Yogu
źródło
1
Czy to podejście nie utrudnia pracy? Z jednej strony używasz klas i instancji, a potem nagle zaczynasz ponownie deklarować dane w zwykłych, starych obiektach JS ... Używanie klas statycznych również poprawia czytelność, nie chodzi tylko o wewnętrzne działanie języka.
Kokodoko
4
Nie uważam, żeby to zakłócało mój przepływ pracy; wręcz przeciwnie, uważam za bardzo wygodne używanie bezklasowych obiektów JavaScrpit lub po prostu zwykłych funkcji i stałych w module. Jednak staram się również unikać posiadania stanu globalnego tak bardzo, jak to możliwe, więc rzadko potrzebuję czegoś w rodzaju statycznych zmiennych klas.
Yogu
1

Zobacz http://www.basarat.com/2013/04/typescript-static-constructors-for.html

Jest to sposób na „sfałszowanie” statycznego konstruktora. Nie jest to pozbawione niebezpieczeństw - zobacz przywoływany element codeplex .

class Test {
    static foo = "orig";

    // Non void static function
    static stat() {
        console.log("Do any static construction here");
        foo = "static initialized";
        // Required to make function non void
        return null;
    }
    // Static variable assignment
    static statrun = Test.stat();
}

// Static construction will have been done:
console.log(Test.foo);
Simon_Weaver
źródło
1

Jednym z możliwych sposobów osiągnięcia tego jest posiadanie statycznych instancji klasy w innej klasie. Na przykład:

class SystemParams
{
  pageWidth:  number = 8270;
  pageHeight: number = 11690;  
}

class DocLevelParams
{
  totalPages: number = 0;
}

class Wrapper
{ 
  static System: SystemParams = new SystemParams();
  static DocLevel: DocLevelParams = new DocLevelParams();
}

Następnie można uzyskać dostęp do parametrów za pomocą Wrappera, bez konieczności deklarowania jego instancji. Na przykład:

Wrapper.System.pageWidth = 1234;
Wrapper.DocLevel.totalPages = 10;

Otrzymujesz więc zalety obiektu typu JavaScript (jak opisano w oryginalnym pytaniu), ale z korzyściami wynikającymi z możliwości dodania pisania TypeScript. Ponadto unika się konieczności dodawania słowa „static” przed wszystkimi parametrami w klasie.

Steve Roberts
źródło
1

Dzięki zewnętrznym modułom ES6 można to osiągnąć w następujący sposób:

// privately scoped array
let arr = [];

export let ArrayModule = {
    add: x => arr.push(x),
    print: () => console.log(arr),
}

Zapobiega to użyciu wewnętrznych modułów i przestrzeni nazw, co jest uważane za złą praktykę przez TSLint [1] [2] , pozwala na prywatne i publiczne określanie zakresu oraz zapobiega inicjalizacji niechcianych obiektów klas.

wizzfizz94
źródło
0

Szukałem czegoś podobnego i trafiłem na coś, co nazywa się Singleton Pattern .

Odniesienie: Singleton Pattern

Pracuję nad klasą BulkLoader, aby załadować różne typy plików i chciałem użyć do tego wzorca Singleton. W ten sposób mogę ładować pliki z mojej głównej klasy aplikacji i łatwo pobierać załadowane pliki z innych klas.

Poniżej znajduje się prosty przykład, jak utworzyć menedżera wyników dla gry z TypeScript i wzorcem Singleton.

class SingletonClass {

private static _instance:SingletonClass = new SingletonClass();

private _score:number = 0;

constructor() {
    if(SingletonClass._instance){
        throw new Error("Error: Instantiation failed: Use SingletonDemo.getInstance() instead of new.");
    }
    SingletonClass._instance = this;
}

public static getInstance():SingletonClass
{
    return SingletonClass._instance;
}

public setScore(value:number):void
{
    this._score = value;
}

public getScore():number
{
    return this._score;
}

public addPoints(value:number):void
{
    this._score += value;
}

public removePoints(value:number):void
{
    this._score -= value;
}   }

Następnie w każdym miejscu na innych zajęciach uzyskasz dostęp do Singleton poprzez:

var scoreManager = SingletonClass.getInstance();
scoreManager.setScore(10); scoreManager.addPoints(1);
scoreManager.removePoints(2); console.log( scoreManager.getScore() );
Devin Stokes
źródło
0

Możesz również użyć słowa kluczowego namespacedo uporządkowania zmiennych, klas, metod i tak dalej. Zobacz dok

namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }

    const lettersRegexp = /^[A-Za-z]+$/;
    const numberRegexp = /^[0-9]+$/;

    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }

    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}
jaguar7021
źródło
Zobacz komentarz Floriana powyżej „To jest przestarzałe. Również tslint nie pozwoli ci już tego robić w przypadku modułów i przestrzeni nazw. Przeczytaj tutaj: palantir.github.io/tslint/rules/no-namespace”
reggaeguitar