Statische TypeScript-Klassen

145

Ich wollte von herkömmlichem JS zu TypeScript wechseln, weil mir die C # -ähnliche Syntax gefällt. Mein Problem ist, dass ich nicht herausfinden kann, wie statische Klassen in TypeScript deklariert werden.

In C # verwende ich häufig statische Klassen, um Variablen und Methoden zu organisieren und sie in einer benannten Klasse zusammenzufügen, ohne ein Objekt instanziieren zu müssen. In Vanilla JS habe ich dies mit einem einfachen JS-Objekt gemacht:

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

In TypeScript würde ich lieber meinen C-Sharpy-Ansatz wählen, aber es scheint, dass statische Klassen in TS nicht existieren. Was ist die geeignete Lösung für dieses Problem?

Rayjax
quelle

Antworten:

166

TypeScript ist nicht C #, daher sollten Sie nicht unbedingt dieselben Konzepte von C # in TypeScript erwarten. Die Frage ist, warum Sie statische Klassen wollen?

In C # ist eine statische Klasse einfach eine Klasse, die nicht in Unterklassen unterteilt werden kann und nur statische Methoden enthalten darf. Mit C # können keine Funktionen außerhalb von Klassen definiert werden. In TypeScript ist dies jedoch möglich.

Wenn Sie nach einer Möglichkeit suchen, Ihre Funktionen / Methoden in einen Namespace (dh nicht global) zu stellen, können Sie die Module von TypeScript verwenden, z

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

Damit Sie extern auf Mf () zugreifen können, aber nicht auf s, und Sie können das Modul nicht erweitern.

Weitere Informationen finden Sie in der TypeScript- Spezifikation .

Marcus
quelle
Ein Modul kann also eine statische Methode haben, eine Klasse jedoch nicht? Module können jedoch keine statischen Daten enthalten. Es ist nicht so praktisch wie JS, Daten und Code zu verpacken, ohne etwas instanziieren zu müssen.
dcsan
Es kann nützlich sein, das einzuschließen, das Sie .jsin Ihr aufnehmen müssen html. Also für Angular 2Sie verwenden wahrscheinlich System... so würde System.import("Folder/M");(oder was auch immer der Pfad zu der kompilierten .jsDatei ist) vor dembootstrap import
Serj Sagan
11
Dies ist veraltet. Mit tslint können Sie dies auch nicht mehr für Module und Namespaces tun. Lesen Sie hier: palantir.github.io/tslint/rules/no-namespace
Florian Leitgeb
Ich habe einen Anwendungsfall für statische Klassen. Im Moment habe ich eine Klasse, die nur statische Methoden enthält. Im Projekt müssen wir für jede Klasse eine Konfiguration bereitstellen, die instanziiert werden kann. Wenn ich eine solche Klasse als statisch deklariere, kann ich nicht nur feststellen, dass sie nicht instanziiert wird, sondern auch vermeiden, dass andere Entwickler Instanzmitglieder hinzufügen.
mdarefull
@florian Leitgeb Was ist dann der bevorzugte Weg, eine Klasse mit nur statischen Methoden und / oder abstraktem Schlüsselwort? Das scheint nur beschissen im Vergleich zu einem Modul, das jetzt veraltet zu sein scheint
wired00
180

Abstrakte Klassen sind seit TypeScript 1.6 ein erstklassiger Bürger von TypeScript. Sie können eine abstrakte Klasse nicht instanziieren.

Hier ist ein Beispiel:

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

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

const okay = MyClass.doSomething();

//const errors = new MyClass(); // Error
Fenton
quelle
6
Wenn es um statische Klassen geht, ist dies die beste Antwort, und ich stimme dem zu. Singletonist für das Shared-Memory-Muster derselben Klasseninstanz. Außerdem hat eine statische Klasse per Definition keine Instanz. Sie müssen daher eine Ausnahme auslösen, wenn der Client versucht, sie zu initialisieren.
Loretoparisi
1
Können wir eine abstrakte Klasse machen?
KimchiMan
@KimchiMan - Ja, das abstractSchlüsselwort wird in TypeScript unterstützt.
Fenton
1
Aktualisiert, um zu verwenden abstract! @ KimchiMan - tolle Idee!
Obsidian
3
Ist dies ein besserer Ansatz, als den Konstruktor als privat zu markieren?
Joro Tenev
72

Das Definieren statischer Eigenschaften und Methoden einer Klasse wird in 8.2.1 der Typescript-Sprachspezifikation beschrieben :

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

Wo Point.distance()ist eine statische (oder "Klasse") Methode.

Rob Raisch
quelle
14
Dies zeigt, wie eine statische Methode erstellt wird. Sie beantwortet nicht die Frage nach statischen Klassen (es sei denn, die eigentliche Frage betrifft tatsächlich statische Methoden).
Marcus
18
Danke für den Kommentar. Es wird beschrieben, wie statische Eigenschaften und Methoden erstellt werden, die es zusammen ermöglichen, eine Klasse mit Daten und Funktionen zu erstellen, ohne dass eine Instanziierung erforderlich ist. Obwohl es sich nicht speziell um eine "statische Klasse" handelt, erfüllt es die Anforderungen, die im JavaScript-Beispiel des OP beschrieben sind.
Rob Raisch
c # hatte bis Version 2 keine statischen Klassen. Sie sind nur in c # vorhanden, um zu verhindern, dass Sie sie instanziieren. Sie können das nicht mit Javascript machen, also macht es nicht viel Sinn
Simon_Weaver
4
@ Simon_Weaver Sie können nicht was in Javascript tun? Verhindern, dass Klassen instanziiert werden? Typescript kümmert sich sowieso nicht viel um die Laufzeit, solange Sie einen Kompilierungsfehler erhalten, wenn Sie versuchen, Dinge zu tun, die Sie nicht dürfen. Das ist alles, was wir brauchen.
Alex
Ich denke, was ich meinte war "Wir hatten das statische SCHLÜSSELWORT auf Klassenebene nicht (was bedeutet, dass Sie keine Instanz einer Klasse erstellen können)" bis Version 2. Aber wenn ich es noch einmal lese, denke ich, dass mein Kommentar das irgendwie verpasst hat Punkt trotzdem. Das OP suchte nicht wirklich nach einem 'statischen Schlüsselwort' für die gesamte Klasse
Simon_Weaver
20

Diese Frage ist ziemlich veraltet, aber ich wollte eine Antwort hinterlassen, die die aktuelle Version der Sprache nutzt. Leider gibt es in TypeScript noch keine statischen Klassen. Sie können jedoch eine Klasse schreiben, die sich mit nur geringem Aufwand ähnlich verhält, indem Sie einen privaten Konstruktor verwenden, der die Instanziierung von Klassen von außen verhindert.

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

Mit diesem Snippet können Sie "statische" Klassen verwenden, die dem C # -Gleichstück ähnlich sind, mit dem einzigen Nachteil, dass es noch möglich ist, sie von innen zu instanziieren. Glücklicherweise können Sie Klassen nicht mit privaten Konstruktoren erweitern.

Christian Ivicevic
quelle
9

Dies ist eine Möglichkeit:

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

__static_ctorHier ist ein sofort aufgerufener Funktionsausdruck. Typescript gibt Code aus, um ihn am Ende der generierten Klasse aufzurufen.

Update: Für generische Typen in statischen Konstruktoren, auf die statische Mitglieder nicht mehr verweisen dürfen, benötigen Sie jetzt einen zusätzlichen Schritt:

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();
}

In jedem Fall können Sie natürlich einfach den statischen Konstruktor vom generischen Typ nach der Klasse aufrufen , z.

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

Denken Sie daran, nie Gebrauch thisin __static_ctoroben (offensichtlich).

James Wilkins
quelle
Auf diese Weise wird immer noch ein Konstruktor für die Klasse ausgegeben.
rufen am
1
"This way" ist ein Hack und ändert nicht wie erwartet den normalen Betrieb des Compilers. Auch class SomeClass {}erzeugt einen Konstruktor - kaum wert zu kommentieren , als ob ein neues Thema eingeführt wird. ;) FYI: Es gibt keine echten "Konstruktoren" in JS - nur Funktionen, die ein "this" haben, wenn sie für Objekte oder via aufgerufen werden new. Dies existiert unabhängig von einer "Klasse".
James Wilkins
6

Ich habe heute (31/07/2018) den gleichen Anwendungsfall erhalten und fand, dass dies eine Problemumgehung ist. Es basiert auf meiner Forschung und hat bei mir funktioniert. Erwartung - Um Folgendes in TypeScript zu erreichen:

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

Ich war das:

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

Daher werden wir es wie folgt konsumieren:

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

Das hat bei mir funktioniert. Wenn jemand andere bessere Vorschläge hat, lassen Sie es uns bitte alle hören !!! Vielen Dank...

KoRa
quelle
3

Statische Klassen in Sprachen wie C # existieren, da es keine anderen Konstrukte der obersten Ebene zum Gruppieren von Daten und Funktionen gibt. In JavaScript ist dies jedoch der Fall, und daher ist es viel natürlicher, ein Objekt einfach so zu deklarieren, wie Sie es getan haben. Um die Klassensyntax genauer nachzuahmen, können Sie Methoden wie folgt deklarieren:

const myStaticClass = {
    property: 10,

    method() {

    }
}
Yogu
quelle
1
Verwirrt dieser Ansatz nicht Ihren Workflow? Einerseits verwenden Sie Klassen und Instanzen, und dann deklarieren Sie plötzlich wieder Daten in regulären alten JS-Objekten ... Die Verwendung statischer Klassen verbessert auch die Lesbarkeit, es geht nicht nur um das Innenleben der Sprache.
Kokodoko
4
Ich finde es nicht störend für meinen Workflow. Im Gegenteil, ich finde es sehr praktisch, klassenlose JavaScrpit-Objekte oder einfach nur Funktionen und Konstanten in einem Modul zu verwenden. Ich versuche jedoch auch, den globalen Status so weit wie möglich zu vermeiden, sodass ich selten so etwas wie statische Klassenvariablen benötige.
Yogu
1

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

Dies ist eine Möglichkeit, einen statischen Konstruktor zu "fälschen". Es ist nicht ungefährlich - siehe den Codeplex-Artikel, auf den verwiesen wird .

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
quelle
1

Eine Möglichkeit, dies zu erreichen, besteht darin, statische Instanzen einer Klasse in einer anderen Klasse zu haben. Beispielsweise:

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();
}

Dann kann mit Wrapper auf Parameter zugegriffen werden, ohne dass eine Instanz davon deklariert werden muss. Beispielsweise:

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

Sie erhalten also die Vorteile des JavaScript-Typobjekts (wie in der ursprünglichen Frage beschrieben), aber die Vorteile, dass Sie die TypeScript-Typisierung hinzufügen können. Außerdem wird vermieden, dass vor allen Parametern in der Klasse 'static' hinzugefügt werden muss.

Steve Roberts
quelle
1

Mit externen ES6-Modulen kann dies folgendermaßen erreicht werden:

// privately scoped array
let arr = [];

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

Dies verhindert die Verwendung interner Module und Namespaces, was von TSLint [1] [2] als schlechte Praxis angesehen wird , ermöglicht privates und öffentliches Scoping und verhindert die Initialisierung unerwünschter Klassenobjekte.

wizzfizz94
quelle
0

Ich suchte nach etwas Ähnlichem und stieß auf etwas namens Singleton Pattern .

Referenz: Singleton-Muster

Ich arbeite an einer BulkLoader-Klasse, um verschiedene Dateitypen zu laden, und wollte das Singleton-Muster dafür verwenden. Auf diese Weise kann ich Dateien aus meiner Hauptanwendungsklasse laden und die geladenen Dateien einfach aus anderen Klassen abrufen.

Im Folgenden finden Sie ein einfaches Beispiel, wie Sie mit TypeScript und dem Singleton-Muster einen Score-Manager für ein Spiel erstellen können.

Klasse 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;
}   }

Dann würden Sie überall in Ihren anderen Klassen Zugriff auf den Singleton erhalten, indem Sie:

var scoreManager = SingletonClass.getInstance();
scoreManager.setScore(10); scoreManager.addPoints(1);
scoreManager.removePoints(2); console.log( scoreManager.getScore() );
Devin Stokes
quelle
0

Sie können auch Schlüsselwörter verwenden namespace, um Ihre Variablen, Klassen, Methoden usw. zu organisieren. Siehe doc

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
quelle
Siehe Florians Kommentar oben "Dies ist veraltet. Auch tslint lässt Sie das für Module und Namespaces nicht mehr tun. Lesen Sie hier: palantir.github.io/tslint/rules/no-namespace"
reggaeguitar