Статические классы TypeScript


152

Я хотел перейти на TypeScript с традиционного JS, потому что мне нравится синтаксис, подобный C #. Моя проблема в том, что я не могу понять, как объявлять статические классы в TypeScript.

В C # я часто использую статические классы для организации переменных и методов, объединяя их в именованный класс, без необходимости инстанцировать объект. В vanilla JS я делал это с помощью простого объекта JS:

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

В TypeScript я бы предпочел использовать свой подход C-sharpy, но кажется, что статических классов в TS не существует. Какое подходящее решение этой проблемы?

Ответы:


172

TypeScript - это не C #, поэтому не обязательно ожидать тех же концепций C # в TypeScript. Вопрос в том, зачем вам статические классы?

В C # статический класс - это просто класс, который не может быть разделен на подклассы и должен содержать только статические методы. C # не позволяет определять функции вне классов. Однако в TypeScript это возможно.

Если вы ищете способ поместить свои функции / методы в пространство имен (т.е. не глобальное), вы можете рассмотреть возможность использования модулей TypeScript, например

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

Так что вы можете получить доступ к Mf () извне, но не к s, и вы не можете расширить модуль.

См. Спецификацию TypeScript для получения более подробной информации.


Итак, у модуля может быть статический метод, а у класса - нет? но модули не могут иметь статических данных. Это не так удобно, как JS, для упаковки данных и кода без необходимости создавать что-либо.
— dcsan

Может быть полезно указать, что вам нужно будет включить .jsв ваш html. Так что, поскольку Angular 2вы, вероятно, используете System... так и сделал бы System.import("Folder/M");(или как бы там ни был путь к скомпилированному .jsфайлу) доbootstrap import
— Серж Саган

12
Это устарело. Также tslint больше не позволит вам делать это для модулей и пространств имен. Читайте здесь: palantir.github.io/tslint/rules/no-namespace
— Флориан Лейтгеб,

У меня есть вариант использования статических классов. Прямо сейчас у меня есть класс, содержащий только статические методы. В проекте нам нужно предоставить какую-то конфигурацию для каждого класса, который может быть создан. Объявление такого класса как static не только поможет мне заметить, что он не будет создан, но и позволит избежать добавления других разработчиков к нему членов экземпляра.
— mdarefull

@florian leitgeb, что в таком случае предпочтительнее, класс с только статическими методами и / или абстрактным ключевым словом? Это кажется паршивым по сравнению с модулем, который теперь кажется устаревшим
— wired00

192

Абстрактные классы были первоклассными гражданами TypeScript начиная с TypeScript 1.6. Вы не можете создать абстрактный класс.

Вот пример:

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

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

const okay = MyClass.doSomething();

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

6
При работе со статическими классами это лучший ответ, и я поддерживаю его. Singletonпредназначен для шаблона общей памяти того же экземпляра класса. Кроме того, статический класс не имеет экземпляра по определению, поэтому вы должны создать исключение, если клиент пытается его инициализировать.
— loretoparisi

1
Можем ли мы создать абстрактный класс?
— KimchiMan 01

@KimchiMan - да, abstractключевое слово поддерживается в TypeScript.
— Fenton

2
Обновлено для использования abstract! @KimchiMan - отличная идея!
— mattyb

3
Это лучший подход, чем пометить конструктор как закрытый?
— Йоро Тенев

74

Определение статических свойств и методов класса описано в разделе 8.2.1 Спецификации языка машинописного текста :

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()- статический (или «классовый») метод.


15
Это показывает, как создать статический метод , но не отвечает на вопрос о статических классах (если только реальный вопрос не касается статических методов).
— Маркус

19
Спасибо за комментарий. В нем описывается, как создавать статические свойства и методы, которые вместе взятые позволяют создать класс с данными и функциями без необходимости создания экземпляра. Хотя он и не является «статическим классом», он удовлетворяет требованиям, описанным в примере JavaScript OP.
— Rob Raisch

В C # не было статических классов до версии 2. они существуют в C # только для того, чтобы вы не создавали их экземпляры. вы не можете сделать это с помощью javascript, поэтому это не имеет особого смысла
— Simon_Weaver

4
@Simon_Weaver Что нельзя делать в javascript? Запретить создание экземпляров классов? В любом случае, Typescript не заботится о времени выполнения, пока вы получаете ошибку компиляции, когда пытаетесь делать то, что вам не разрешено, это все, что нам нужно.
— Alex

Я предполагаю, что я имел в виду, что у нас не было статического KEYWORD на уровне класса (что означает, что вы не можете создать экземпляр класса) до версии 2. но, читая его снова, я думаю, что мой комментарий вроде пропустил точка в любом случае. OP на самом деле не искал «статическое ключевое слово» для всего класса
— Simon_Weaver 01

20

Этот вопрос довольно устарел, но я хотел оставить ответ, который использует текущую версию языка. К сожалению, статические классы все еще не существуют в TypeScript, однако вы можете написать класс, который ведет себя аналогичным образом с небольшими накладными расходами, используя частный конструктор, который предотвращает создание экземпляров классов извне.

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

Этот фрагмент кода позволит вам использовать «статические» классы, аналогичные аналогам C #, с единственным недостатком, что их все еще можно создавать изнутри. К счастью, вы не можете расширять классы с помощью частных конструкторов.


9

Это один способ:

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

__static_ctorвот выражение функции, вызываемое немедленно. Typescript выведет код для его вызова в конце сгенерированного класса.

Обновление: для общих типов в статических конструкторах, на которые больше не могут ссылаться статические члены, вам понадобится дополнительный шаг:

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

В любом случае, конечно, вы можете просто вызвать статический конструктор универсального типа после класса, например:

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

Только не забудьте использовать NEVER thisв __static_ctorвыше (очевидно).


Таким образом, по-прежнему создается конструктор класса.
— theycallmemorty

1
«Этот способ» - это взлом, и он не меняет нормальную работу компилятора, как ожидалось. Даже class SomeClass {}генерирует конструктор - вряд ли стоит комментировать, как будто вводится новая проблема. ;) К вашему сведению: в JS нет настоящих «конструкторов» - только функции, у которых есть «this» при вызове объектов или через new. Это существует не смотря ни на что для любого «класса».
— Джеймс Уилкинс

7

Сегодня у меня такой же вариант использования (31.07.2018), и я обнаружил, что это обходной путь. Он основан на моих исследованиях и у меня сработал. Ожидание - для достижения следующих целей в TypeScript:

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

Я сделал это:

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

Следовательно, мы будем потреблять его, как показано ниже:

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

Это сработало для меня. Если у кого-то есть другие предложения получше, дайте нам всем это услышать !!! Благодарность...


3

Статические классы в таких языках, как C #, существуют потому, что нет других конструкций верхнего уровня для группировки данных и функций. Однако в JavaScript они это делают, и поэтому гораздо естественнее просто объявить объект, как вы. Чтобы более точно имитировать синтаксис класса, вы можете объявить такие методы:

const myStaticClass = {
    property: 10,

    method() {

    }
}

1
Разве такой подход не запутывает ваш рабочий процесс? С одной стороны, вы используете классы и экземпляры, а затем внезапно снова начинаете объявлять данные в обычных старых объектах JS ... Использование статических классов также способствует удобочитаемости, это касается не только внутренней работы языка.
— Кокодоко

4
Я не считаю, что это мешает моему рабочему процессу; напротив, мне очень удобно использовать бесклассовые объекты JavaScrpit или просто простые функции и константы в модуле. Однако я также стараюсь максимально избегать глобального состояния, поэтому мне редко нужно что-то вроде статических переменных класса.
— Йогу

1

Увидеть Http://www.basarat.com/2013/04/typescript-static-constructors-for.html

Это способ «подделать» статический конструктор. Это небезопасно - см. Упомянутый элемент 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);

1

Один из возможных способов добиться этого - иметь статические экземпляры класса внутри другого класса. Например:

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

Затем к параметрам можно получить доступ с помощью Wrapper без необходимости объявлять его экземпляр. Например:

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

Таким образом, вы получаете преимущества объекта типа JavaScript (как описано в исходном вопросе), но с преимуществами возможности добавления ввода TypeScript. Кроме того, это позволяет избежать добавления слова static перед всеми параметрами в классе.


1

С внешними модулями ES6 это может быть достигнуто следующим образом:

// privately scoped array
let arr = [];

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

Это предотвращает использование внутренних модулей и пространств имен, что считается плохой практикой TSLint [1] [2] , разрешает частную и открытую область видимости и предотвращает инициализацию нежелательных объектов класса.


0

Я искал что-то подобное и наткнулся на то, что называется Singleton Pattern .

Ссылка: Singleton Pattern

Я работаю над классом BulkLoader для загрузки файлов разных типов и хотел использовать для него шаблон Singleton. Таким образом я могу загружать файлы из основного класса приложения и легко извлекать загруженные файлы из других классов.

Ниже приведен простой пример того, как вы можете создать менеджер очков для игры с TypeScript и шаблоном 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;
}   }

Тогда где угодно в других ваших классах вы получите доступ к синглтону:

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

0

Вы также можете использовать ключевое слово namespaceдля организации ваших переменных, классов, методов и так далее. См. Документ

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

См. Комментарий Флориана выше: «Это устарело. Также tslint не позволит вам больше делать это для модулей и пространств имен. Читайте здесь:
— palantir.github.io/tslint/rules/no-namespace
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.