Clases estáticas de TypeScript

145

Quería pasar a TypeScript desde JS tradicional porque me gusta la sintaxis tipo C #. Mi problema es que no puedo descubrir cómo declarar clases estáticas en TypeScript.

En C #, a menudo uso clases estáticas para organizar variables y métodos, reuniéndolos en una clase con nombre, sin necesidad de instalar un objeto. En vanilla JS, solía hacer esto con un simple objeto JS:

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

En TypeScript, preferiría mi enfoque C-sharpy, pero parece que las clases estáticas no existen en TS. ¿Cuál es la solución adecuada para este problema?

Rayjax
fuente

Respuestas:

166

TypeScript no es C #, por lo que no debe esperar necesariamente los mismos conceptos de C # en TypeScript. La pregunta es ¿por qué quieres clases estáticas?

En C #, una clase estática es simplemente una clase que no se puede subclasificar y debe contener solo métodos estáticos. C # no le permite a uno definir funciones fuera de las clases. Sin embargo, en TypeScript esto es posible.

Si está buscando una manera de poner sus funciones / métodos en un espacio de nombres (es decir, no global), podría considerar usar los módulos de TypeScript, por ejemplo

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

Para que pueda acceder a Mf () externamente, pero no s, y no puede extender el módulo.

Consulte la especificación de TypeScript para más detalles.

Marcus
fuente
entonces un módulo puede tener un método estático, pero una clase no? pero los módulos no pueden tener datos estáticos. No es tan conveniente como JS para envolver datos y código sin tener que instanciar nada.
dcsan
Puede ser útil incluir que necesitará incluir el .jsen su html. Entonces Angular 2, probablemente esté usando System... también lo haría System.import("Folder/M");(o lo que sea la ruta al .jsarchivo compilado ) antes delbootstrap import
Serj Sagan
11
Esto está en desuso. Además, tslint ya no te permitirá hacer eso para módulos y espacios de nombres. Lea aquí: palantir.github.io/tslint/rules/no-namespace
Florian Leitgeb
Tengo un caso de uso para tener clases estáticas. En este momento, tengo una clase que solo contiene métodos estáticos. En el proyecto necesitamos proporcionar algún tipo de configuración para cada clase que pueda ser instanciada. Declarar tal clase como estática no solo me ayudaría a notar que no se instanciará, sino que también evitará que otros desarrolladores le agreguen miembros de instancia.
mdarefull
@florian leitgeb, ¿cuál es la forma preferida entonces, una clase con solo métodos estáticos y / o palabras clave abstractas? Eso solo parece horrible en comparación con el módulo que ahora parece obsoleto
wired00
181

Las clases abstractas han sido ciudadanos de primera clase de TypeScript desde TypeScript 1.6. No se puede crear una instancia de una clase abstracta.

Aquí hay un ejemplo:

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

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

const okay = MyClass.doSomething();

//const errors = new MyClass(); // Error
Fenton
fuente
66
Cuando se trata de clases estáticas, esta es la mejor respuesta y yo voté a favor. Singletones para el patrón de memoria compartida de la misma instancia de clase. Además, una clase estática no tiene instancia por definición, por lo que debe lanzar una excepción si el cliente intenta inicializarla.
loretoparisi
1
¿Podemos hacer una clase abstracta?
KimchiMan 01 de
@KimchiMan: sí, la abstractpalabra clave es compatible con TypeScript.
Fenton
1
Actualizado para usar abstract! @KimchiMan - ¡gran idea!
Obsidiana
3
¿Es este un mejor enfoque que marcar el constructor como privado?
Joro Tenev
72

La definición de propiedades estáticas y métodos de una clase se describe en 8.2.1 de la Especificación del lenguaje mecanografiado :

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

donde Point.distance()es un método estático (o "clase").

Rob Raisch
fuente
14
Esto muestra cómo crear un método estático , no responde la pregunta sobre las clases estáticas (a menos que la pregunta real sea sobre métodos estáticos).
Marcus
18
Gracias por el comentario. Describe cómo crear propiedades y métodos estáticos, que en conjunto permiten crear una clase con datos y funcionalidad sin necesidad de instanciación. Si bien no es específicamente una "clase estática", cumple el requisito descrito en el ejemplo de JavaScript del OP.
Rob Raisch
c # no tenía clases estáticas hasta la versión 2. existen en c # solo para evitar que las instancia. no puedes hacer eso con javascript, por lo que no tiene mucho sentido
Simon_Weaver
44
@Simon_Weaver ¿No puedes hacer qué en JavaScript? ¿Prevenir clases para ser instanciadas? De todas formas, mecanografiar no se preocupa mucho por el tiempo de ejecución, siempre que obtenga un error de compilación cuando intente hacer cosas que no tiene permitido, eso es todo lo que necesitamos.
Alex
Supongo que lo que quise decir fue 'no teníamos la PALABRA CLAVE estática a nivel de clase (lo que significa que no se puede crear una instancia de una clase)' hasta la versión 2. pero al leerla nuevamente creo que mi comentario se perdió el señalar de todos modos. el OP no estaba realmente buscando una 'palabra clave estática' para toda la clase
Simon_Weaver
20

Esta pregunta está bastante anticuada, pero quería dejar una respuesta que aproveche la versión actual del idioma. Desafortunadamente, las clases estáticas aún no existen en TypeScript, sin embargo, puede escribir una clase que se comporte de manera similar con solo una pequeña sobrecarga utilizando un constructor privado que evite la creación de instancias de clases desde el exterior.

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

Este fragmento le permitirá usar clases "estáticas" similares a la contraparte de C # con el único inconveniente de que aún es posible crear instancias desde adentro. Afortunadamente, no puedes extender clases con constructores privados.

Christian Ivicevic
fuente
9

Esta es una forma:

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

__static_ctorAquí hay una expresión de función invocada inmediatamente. El mecanografiado generará código para llamarlo al final de la clase generada.

Actualización: Para los tipos genéricos en constructores estáticos, a los que los miembros estáticos ya no pueden hacer referencia, ahora necesitará un paso adicional:

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

En cualquier caso, por supuesto, puede llamar al constructor estático de tipo genérico después de la clase, como:

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

Sólo recuerde utilizar NUNCA thisen el __static_ctoranterior (obviamente).

James Wilkins
fuente
De esta manera todavía se emite un constructor para la clase.
conmemoran el
1
"De esta manera" es un truco, y no cambia el funcionamiento normal del compilador, como se esperaba. Incluso class SomeClass {}genera un constructor, apenas vale la pena comentarlo como si se introdujera un nuevo problema. ;) FYI: No hay "constructores" reales en JS, solo funciones que tienen un "esto" cuando se invoca en objetos, o vía new. Esto existe sin importar qué para cualquier "clase".
James Wilkins el
6

Obtuve el mismo caso de uso hoy (31/07/2018) y encontré que esto es una solución alternativa. Se basa en mi investigación y funcionó para mí. Expectativa : lograr lo siguiente en TypeScript:

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

Hice esto:

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

Por lo tanto, lo consumiremos de la siguiente manera:

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

Esto funcionó para mí. Si alguien tiene otras mejores sugerencias, ¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡Si En quien más te guste la ayuda! Gracias...

KoRa
fuente
3

Las clases estáticas en lenguajes como C # existen porque no hay otras construcciones de nivel superior para agrupar datos y funciones. Sin embargo, en JavaScript lo hacen, por lo que es mucho más natural declarar un objeto como lo hizo. Para imitar más de cerca la sintaxis de la clase, puede declarar métodos como este:

const myStaticClass = {
    property: 10,

    method() {

    }
}
Yogu
fuente
1
¿Este enfoque no confunde su flujo de trabajo? Por un lado, usa clases e instancias, y luego, de repente, comienza a declarar datos en objetos JS antiguos de nuevo ... El uso de clases estáticas también ayuda a la legibilidad, no se trata solo del funcionamiento interno del lenguaje.
Kokodoko el
44
No lo encuentro jugando con mi flujo de trabajo; por el contrario, me parece muy conveniente usar objetos JavaScrpit sin clases o simplemente funciones y constantes simples en un módulo. Sin embargo, también trato de evitar tener un estado global tanto como sea posible, por lo que rara vez necesito algo como variables de clase estáticas.
Yogu
1

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

Esta es una manera de 'falsificar' un constructor estático. No está exento de peligros: consulte el elemento codeplex referenciado .

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

Una posible forma de lograr esto es tener instancias estáticas de una clase dentro de otra clase. Por ejemplo:

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

Luego, se puede acceder a los parámetros utilizando Wrapper, sin tener que declarar una instancia del mismo. Por ejemplo:

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

Por lo tanto, obtiene los beneficios del objeto de tipo JavaScript (como se describe en la pregunta original) pero con los beneficios de poder agregar la escritura TypeScript. Además, evita tener que agregar 'estática' delante de todos los parámetros de la clase.

Steve Roberts
fuente
1

Con los módulos externos ES6 esto se puede lograr así:

// privately scoped array
let arr = [];

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

Esto evita el uso de módulos internos y espacios de nombres que TSLint [1] [2] considera una mala práctica , permite el alcance privado y público y evita la inicialización de objetos de clase no deseados.

wizzfizz94
fuente
0

Estaba buscando algo similar y me encontré con algo llamado Singleton Pattern.

Referencia: Patrón Singleton

Estoy trabajando en una clase BulkLoader para cargar diferentes tipos de archivos y quería usar el patrón Singleton para ello. De esta manera puedo cargar archivos de mi clase de aplicación principal y recuperar los archivos cargados fácilmente de otras clases.

A continuación se muestra un ejemplo simple de cómo puede hacer un administrador de puntajes para un juego con TypeScript y el patrón Singleton.

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

Luego, en cualquier otro lugar de sus otras clases, tendrá acceso al Singleton de la siguiente manera:

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

También puede usar palabras clave namespacepara organizar sus variables, clases, métodos, etc. Ver 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
fuente
Vea el comentario de Florian arriba "Esto está en desuso. Además, tslint ya no le permitirá hacer eso para módulos y espacios de nombres. Lea aquí: palantir.github.io/tslint/rules/no-namespace"
reggaeguitar