Classes statiques TypeScript


146

Je voulais passer à TypeScript à partir du JS traditionnel car j'aime la syntaxe de type C #. Mon problème est que je ne peux pas savoir comment déclarer des classes statiques dans TypeScript.

En C #, j'utilise souvent des classes statiques pour organiser les variables et les méthodes, en les mettant ensemble dans une classe nommée, sans avoir besoin d'instatier un objet. Dans vanilla JS, j'avais l'habitude de faire cela avec un simple objet JS:

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

Dans TypeScript, je préférerais mon approche C-sharpy, mais il semble que les classes statiques n'existent pas dans TS. Quelle est la solution appropriée à ce problème?

Réponses:


166

TypeScript n'est pas C #, vous ne devriez donc pas vous attendre nécessairement aux mêmes concepts de C # dans TypeScript. La question est pourquoi voulez-vous des classes statiques?

En C #, une classe statique est simplement une classe qui ne peut pas être sous-classée et ne doit contenir que des méthodes statiques. C # ne permet pas de définir des fonctions en dehors des classes. Dans TypeScript, cela est cependant possible.

Si vous cherchez un moyen de mettre vos fonctions / méthodes dans un espace de noms (c'est-à-dire non global), vous pouvez envisager d'utiliser les modules de TypeScript, par exemple

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

Pour que vous puissiez accéder à Mf () en externe, mais pas à s, et vous ne pouvez pas étendre le module.

Consultez la spécification TypeScript pour plus de détails.


donc un module peut avoir une méthode statique, mais une classe ne peut pas? mais les modules ne peuvent pas avoir de données statiques. Ce n'est pas aussi pratique que JS pour encapsuler des données et du code sans avoir à instancier quoi que ce soit.
dcsan

Il peut être utile d'inclure que vous devrez inclure le .jsdans votre fichier html. Donc, pour Angular 2vous utilisez probablement System... alors ferait System.import("Folder/M");(ou quel que soit le chemin d'accès au .jsfichier compilé ) avant lebootstrap import
Serj Sagan

11
Ceci est obsolète. De plus, tslint ne vous laissera plus faire cela pour les modules et les espaces de noms. Lire ici: palantir.github.io/tslint/rules/no-namespace
Florian Leitgeb

J'ai un cas d'utilisation pour avoir des classes statiques. Pour le moment, j'ai une classe qui ne contient que des méthodes statiques. Dans le projet, nous devons fournir une sorte de configuration pour chaque classe qui peut être instanciée. Déclarer une telle classe comme statique m'aiderait non seulement à remarquer qu'elle ne sera pas instanciée, mais éviterait également que d'autres développeurs lui ajoutent des membres d'instance.
mdarefull

@florian leitgeb quel est donc le moyen préféré, une classe avec uniquement des méthodes statiques et / ou un mot-clé abstrait? Cela semble juste merdique comparé au module qui semble maintenant obsolète
wired00

182

Les classes abstraites ont été un citoyen de première classe de TypeScript depuis TypeScript 1.6. Vous ne pouvez pas instancier une classe abstraite.

Voici un exemple:

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

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

const okay = MyClass.doSomething();

//const errors = new MyClass(); // Error

6
Lorsqu'il s'agit de classes statiques, c'est la meilleure réponse et je l'approuve. Singletonest pour le modèle de mémoire partagée de la même instance de classe. De plus, une classe statique n'a pas d'instance par définition, vous devez donc lever une exception si le client tente de l'initialiser.
loretoparisi

1
Pouvons-nous faire une classe abstraite?
KimchiMan

@KimchiMan - oui, le abstractmot-clé est pris en charge dans TypeScript.
Fenton

1
Mise à jour à utiliser abstract! @KimchiMan - super idée!
Obsidian

3
Est-ce une meilleure approche que de marquer le constructeur comme privé?
Joro Tenev

72

La définition des propriétés statiques et des méthodes d'une classe est décrite dans 8.2.1 de la spécification du langage 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); 
  } 
}

Point.distance()est une méthode statique (ou "classe").


14
Cela montre comment créer une méthode statique , cela ne répond pas à la question qui concerne les classes statiques (sauf si la vraie question concerne en fait les méthodes statiques).
Marcus

18
Merci pour le commentaire. Il décrit comment créer des propriétés et des méthodes statiques, qui, prises ensemble, permettent de créer une classe avec des données et des fonctionnalités sans avoir besoin d'une instanciation. Bien qu'il ne s'agisse pas spécifiquement d'une «classe statique», elle remplit les conditions décrites dans l'exemple JavaScript de l'OP.
Rob Raisch

c # n'avait pas de classes statiques jusqu'à la version 2. elles n'existent en c # que pour vous empêcher de les instancier. vous ne pouvez pas faire ça avec javascript donc ça n'a pas beaucoup de sens
Simon_Weaver

4
@Simon_Weaver Vous ne pouvez pas faire quoi en javascript? Empêcher les classes d'être instanciées? De toute façon, Typescript ne se soucie pas beaucoup de l'exécution, tant que vous obtenez une erreur de compilation lorsque vous essayez de faire des choses auxquelles vous n'êtes pas autorisé, c'est tout ce dont nous avons besoin.
Alex

Je suppose que ce que je voulais dire était `` nous n'avions pas le MOT-CLÉ statique au niveau de la classe (ce qui signifie que vous ne pouvez pas créer une instance d'une classe) '' jusqu'à la version 2. mais en la relisant, je pense que mon commentaire a manqué le point de toute façon. l'OP ne cherchait pas vraiment un 'mot-clé statique' pour toute la classe
Simon_Weaver

20

Cette question est assez datée mais je voulais laisser une réponse qui tire parti de la version actuelle du langage. Malheureusement, les classes statiques n'existent toujours pas dans TypeScript, mais vous pouvez écrire une classe qui se comporte de manière similaire avec seulement une petite surcharge en utilisant un constructeur privé qui empêche l'instanciation des classes de l'extérieur.

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

Cet extrait de code vous permettra d'utiliser des classes "statiques" similaires à l'homologue C # avec le seul inconvénient qu'il est toujours possible de les instancier de l'intérieur. Heureusement, vous ne pouvez pas étendre les classes avec des constructeurs privés.


9

C'est une façon:

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

__static_ctorvoici une expression de fonction immédiatement invoquée. Typescript produira du code pour l'appeler à la fin de la classe générée.

Mise à jour: pour les types génériques dans les constructeurs statiques, qui ne sont plus autorisés à être référencés par des membres statiques, vous aurez besoin d'une étape supplémentaire maintenant:

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

Dans tous les cas, bien sûr, vous pouvez simplement appeler le constructeur statique de type générique après la classe, comme:

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

Rappelez - vous de ne jamais utiliser thisen __static_ctorhaut (évidemment).


Cette manière émet toujours un constructeur pour la classe.
theycallmemorty

1
"This way" est un hack, et ne change pas le fonctionnement normal du compilateur, comme prévu. Génère même class SomeClass {}un constructeur - il ne vaut guère la peine d'être commenté comme si un nouveau problème était introduit. ;) FYI: Il n'y a pas de vrais "constructeurs" dans JS - seulement des fonctions qui ont un "this" lorsqu'elles sont appelées sur des objets, ou via new. Cela existe quoi qu'il arrive pour n'importe quelle «classe».
James Wilkins

6

J'ai eu le même cas d'utilisation aujourd'hui (31/07/2018) et j'ai trouvé que c'était une solution de contournement. Il est basé sur mes recherches et cela a fonctionné pour moi. Attente - Pour atteindre les objectifs suivants dans TypeScript:

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

J'ai fait ça:

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

Par conséquent, nous le consommerons comme ci-dessous:

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

Cela a fonctionné pour moi. Si quelqu'un a d'autres meilleures suggestions, laissez-nous tous l'entendre !!! Merci...


3

Les classes statiques dans des langages comme C # existent car il n'y a pas d'autres constructions de niveau supérieur pour regrouper des données et des fonctions. En JavaScript, cependant, ils le font et il est donc beaucoup plus naturel de simplement déclarer un objet comme vous l'avez fait. Pour imiter plus étroitement la syntaxe de la classe, vous pouvez déclarer des méthodes comme ceci:

const myStaticClass = {
    property: 10,

    method() {

    }
}

1
Cette approche ne perturbe-t-elle pas votre flux de travail? D'une part, vous utilisez des classes et des instances, puis soudainement vous recommencez à déclarer des données dans d'anciens objets JS normaux ... L'utilisation de classes statiques facilite également la lisibilité, il ne s'agit pas seulement du fonctionnement interne du langage.
Kokodoko

4
Je ne trouve pas que cela perturbe mon flux de travail; au contraire, je trouve très pratique d'utiliser des objets JavaScrpit sans classe ou simplement des fonctions et des constantes simples dans un module. Cependant, j'essaie aussi d'éviter autant que possible d'avoir un état global, donc j'ai rarement besoin de quelque chose comme des variables de classe statiques.
Yogu

1

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

C'est une façon de «simuler» un constructeur statique. Ce n'est pas sans dangers - voir l' élément codeplex référencé .

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

1

Une façon possible d'y parvenir est d'avoir des instances statiques d'une classe dans une autre classe. Par exemple:

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

Ensuite, les paramètres sont accessibles à l'aide de Wrapper, sans avoir à en déclarer une instance. Par exemple:

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

Ainsi, vous bénéficiez des avantages de l'objet de type JavaScript (comme décrit dans la question d'origine) mais avec les avantages de pouvoir ajouter le typage TypeScript. De plus, cela évite d'avoir à ajouter «statique» devant tous les paramètres de la classe.


1

Avec les modules externes ES6, cela peut être réalisé comme suit:

// privately scoped array
let arr = [];

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

Cela empêche l'utilisation de modules internes et d'espaces de noms, ce qui est considéré comme une mauvaise pratique par TSLint [1] [2] , permet une portée privée et publique et empêche l'initialisation d'objets de classe indésirables.


0

Je cherchais quelque chose de similaire et suis tombé sur quelque chose appelé le Singleton Pattern.

Référence: Singleton Pattern

Je travaille sur une classe BulkLoader pour charger différents types de fichiers et je voulais utiliser le modèle Singleton pour cela. De cette façon, je peux charger des fichiers depuis ma classe d'application principale et récupérer facilement les fichiers chargés à partir d'autres classes.

Vous trouverez ci-dessous un exemple simple de la façon dont vous pouvez créer un gestionnaire de partition pour un jeu avec TypeScript et le modèle 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;
}   }

Ensuite, n'importe où dans vos autres classes, vous auriez accès au Singleton en:

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

0

Vous pouvez également utiliser des mots clés namespacepour organiser vos variables, classes, méthodes, etc. Voir 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);
        }
    }
}

Voir le commentaire de Florian ci-dessus "Ceci est obsolète. De plus, tslint ne vous laissera plus faire cela pour les modules et les espaces de noms. Lisez ici: palantir.github.io/tslint/rules/no-namespace"
reggaeguitar
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.