Статические классы 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.